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.
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.0.dist-info/METADATA +300 -0
  460. oscura-0.1.0.dist-info/RECORD +463 -0
  461. oscura-0.1.0.dist-info/entry_points.txt +2 -0
  462. {oscura-0.0.1.dist-info → oscura-0.1.0.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.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,691 @@
1
+ """Configurable multi-bit parallel bus decoding.
2
+
3
+ This module provides configurable bus decoding for parallel digital signals,
4
+ supporting various bit orderings, active-low signaling, and clock-based
5
+ or interval-based sampling strategies.
6
+
7
+
8
+ Example:
9
+ >>> import numpy as np
10
+ >>> from oscura.analyzers.digital.bus import BusConfig, BusDecoder
11
+ >>> # Define 8-bit bus configuration
12
+ >>> config = BusConfig(name="data_bus", width=8, bit_order='lsb_first')
13
+ >>> config.bits = [{'channel': i, 'bit': i, 'name': f'D{i}'} for i in range(8)]
14
+ >>> # Create decoder
15
+ >>> decoder = BusDecoder(config, sample_rate=100e6)
16
+ >>> # Decode bus values from bit traces
17
+ >>> bit_traces = {i: np.random.randint(0, 2, 1000, dtype=np.uint8) for i in range(8)}
18
+ >>> transactions = decoder.decode_bus(bit_traces)
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ from dataclasses import dataclass, field
24
+ from pathlib import Path
25
+ from typing import TYPE_CHECKING, Any, Literal
26
+
27
+ import numpy as np
28
+
29
+ if TYPE_CHECKING:
30
+ from numpy.typing import NDArray
31
+
32
+
33
+ @dataclass
34
+ class BusConfig:
35
+ """Configuration for parallel bus decoding.
36
+
37
+ Attributes:
38
+ name: Descriptive name for the bus.
39
+ width: Number of bits in the bus.
40
+ bit_order: Bit ordering, 'lsb_first' or 'msb_first'.
41
+ active_low: Whether signals are active-low (inverted).
42
+ bits: List of bit definitions with channel mapping.
43
+
44
+ Example:
45
+ >>> config = BusConfig(name="addr_bus", width=12, bit_order='lsb_first')
46
+ >>> config.bits = [{'channel': i, 'bit': i} for i in range(12)]
47
+ """
48
+
49
+ name: str
50
+ width: int # Number of bits
51
+ bit_order: Literal["lsb_first", "msb_first"] = "lsb_first"
52
+ active_low: bool = False
53
+ bits: list[dict[str, Any]] = field(
54
+ default_factory=list
55
+ ) # [{channel: 0, bit: 0, name: 'D0'}, ...]
56
+
57
+ def __post_init__(self) -> None:
58
+ """Validate bus configuration."""
59
+ if self.width <= 0:
60
+ raise ValueError(f"Bus width must be positive, got {self.width}")
61
+ if self.bit_order not in ["lsb_first", "msb_first"]:
62
+ raise ValueError(f"Invalid bit_order: {self.bit_order}")
63
+
64
+ @classmethod
65
+ def from_yaml(cls, path: str | Path) -> BusConfig:
66
+ """Load bus configuration from YAML file.
67
+
68
+ Args:
69
+ path: Path to YAML configuration file.
70
+
71
+ Returns:
72
+ BusConfig instance loaded from file.
73
+
74
+ Raises:
75
+ ImportError: If PyYAML is not installed.
76
+ FileNotFoundError: If file does not exist.
77
+ """
78
+ try:
79
+ import yaml
80
+ except ImportError as e:
81
+ raise ImportError(
82
+ "PyYAML is required for YAML loading. Install with: pip install pyyaml"
83
+ ) from e
84
+
85
+ path = Path(path)
86
+ if not path.exists():
87
+ raise FileNotFoundError(f"Configuration file not found: {path}")
88
+
89
+ with open(path) as f:
90
+ config_dict = yaml.safe_load(f)
91
+
92
+ return cls.from_dict(config_dict)
93
+
94
+ @classmethod
95
+ def from_dict(cls, config: dict[str, Any]) -> BusConfig:
96
+ """Create bus configuration from dictionary.
97
+
98
+ Args:
99
+ config: Dictionary with bus configuration parameters.
100
+
101
+ Returns:
102
+ BusConfig instance created from dictionary.
103
+
104
+ Example:
105
+ >>> config_dict = {
106
+ ... 'name': 'data_bus',
107
+ ... 'width': 8,
108
+ ... 'bit_order': 'lsb_first',
109
+ ... 'bits': [{'channel': i, 'bit': i} for i in range(8)]
110
+ ... }
111
+ >>> config = BusConfig.from_dict(config_dict)
112
+ """
113
+ return cls(
114
+ name=config.get("name", "bus"),
115
+ width=config["width"],
116
+ bit_order=config.get("bit_order", "lsb_first"),
117
+ active_low=config.get("active_low", False),
118
+ bits=config.get("bits", []),
119
+ )
120
+
121
+
122
+ @dataclass
123
+ class ParallelBusConfig:
124
+ """Configuration for parallel bus decoding with simplified interface.
125
+
126
+ This is a convenience class for tests that provides a simpler interface
127
+ than the full BusConfig.
128
+
129
+ Attributes:
130
+ data_width: Number of data bits in the bus.
131
+ bit_order: Bit ordering, 'lsb_first' or 'msb_first'.
132
+ has_clock: Whether the bus uses a clock signal.
133
+ address_width: Optional number of address bits.
134
+ active_low: Whether signals are active-low.
135
+
136
+ Example:
137
+ >>> config = ParallelBusConfig(data_width=8, bit_order='lsb_first')
138
+ """
139
+
140
+ data_width: int
141
+ bit_order: Literal["lsb_first", "msb_first"] = "lsb_first"
142
+ has_clock: bool = False
143
+ address_width: int | None = None
144
+ active_low: bool = False
145
+
146
+ def __post_init__(self) -> None:
147
+ """Validate configuration."""
148
+ if self.data_width <= 0:
149
+ raise ValueError(f"data_width must be positive, got {self.data_width}")
150
+ if self.address_width is not None and self.address_width <= 0:
151
+ raise ValueError(f"address_width must be positive, got {self.address_width}")
152
+
153
+ def to_bus_config(self, name: str = "parallel_bus") -> BusConfig:
154
+ """Convert to BusConfig.
155
+
156
+ Args:
157
+ name: Name for the bus configuration.
158
+
159
+ Returns:
160
+ BusConfig instance.
161
+ """
162
+ return BusConfig(
163
+ name=name,
164
+ width=self.data_width,
165
+ bit_order=self.bit_order,
166
+ active_low=self.active_low,
167
+ bits=[{"channel": i, "bit": i} for i in range(self.data_width)],
168
+ )
169
+
170
+
171
+ @dataclass
172
+ class BusTransaction:
173
+ """A decoded bus transaction.
174
+
175
+ Attributes:
176
+ timestamp: Time in seconds when transaction occurred.
177
+ sample_index: Sample index in the original traces.
178
+ value: Decoded bus value as integer.
179
+ raw_bits: Individual bit values (after active-low inversion if applicable).
180
+ transaction_type: Optional transaction type label.
181
+ address: Optional address field if this is an address bus.
182
+ data: Optional data field if this is a data bus.
183
+ """
184
+
185
+ timestamp: float # Time in seconds
186
+ sample_index: int
187
+ value: int # Decoded bus value
188
+ raw_bits: list[int] # Individual bit values
189
+ transaction_type: str = "" # 'read', 'write', etc.
190
+ address: int | None = None # If address bus present
191
+ data: int | None = None # If data value
192
+
193
+
194
+ class BusDecoder:
195
+ """Decode multi-bit parallel buses from individual bit traces.
196
+
197
+ Supports configurable bit ordering, active-low signaling, and various
198
+ sampling strategies (clock-based or interval-based).
199
+
200
+ Attributes:
201
+ config: Bus configuration specifying width, ordering, etc.
202
+ sample_rate: Sample rate of input traces in Hz.
203
+
204
+ Example:
205
+ >>> config = BusConfig(name="data", width=8, bit_order='lsb_first')
206
+ >>> decoder = BusDecoder(config, sample_rate=100e6)
207
+ >>> bit_traces = {i: trace_data for i in range(8)}
208
+ >>> transactions = decoder.decode_bus(bit_traces)
209
+ """
210
+
211
+ def __init__(
212
+ self,
213
+ config: BusConfig | ParallelBusConfig,
214
+ sample_rate: float = 1.0,
215
+ ):
216
+ """Initialize decoder with configuration.
217
+
218
+ Args:
219
+ config: Bus configuration (BusConfig or ParallelBusConfig).
220
+ sample_rate: Sample rate of input traces in Hz.
221
+
222
+ Raises:
223
+ ValueError: If sample rate is invalid.
224
+ """
225
+ if sample_rate <= 0:
226
+ raise ValueError(f"Sample rate must be positive, got {sample_rate}")
227
+
228
+ # Handle ParallelBusConfig
229
+ self._parallel_config: ParallelBusConfig | None
230
+ if isinstance(config, ParallelBusConfig):
231
+ self._parallel_config = config
232
+ self.config = config.to_bus_config()
233
+ else:
234
+ self._parallel_config = None
235
+ self.config = config
236
+
237
+ self.sample_rate = sample_rate
238
+ self._time_base = 1.0 / sample_rate
239
+
240
+ def decode_bus(
241
+ self,
242
+ bit_traces: dict[int, NDArray[np.uint8]], # channel_index -> trace data
243
+ clock_trace: NDArray[np.uint8] | None = None,
244
+ clock_edge: Literal["rising", "falling"] = "rising",
245
+ ) -> list[BusTransaction]:
246
+ """Decode bus values from individual bit traces.
247
+
248
+ Args:
249
+ bit_traces: Dictionary mapping channel index to trace data (boolean or 0/1).
250
+ clock_trace: Optional clock signal for synchronous sampling.
251
+ clock_edge: Which clock edge to sample on ('rising' or 'falling').
252
+
253
+ Returns:
254
+ List of BusTransaction objects with decoded values.
255
+
256
+ Raises:
257
+ ValueError: If bit traces don't match configuration.
258
+
259
+ Example:
260
+ >>> bit_traces = {0: np.array([0,1,1,0]), 1: np.array([1,1,0,0])}
261
+ >>> transactions = decoder.decode_bus(bit_traces)
262
+ """
263
+ if not bit_traces:
264
+ raise ValueError("bit_traces cannot be empty")
265
+
266
+ # Use clock-based or interval-based sampling
267
+ if clock_trace is not None:
268
+ return self.sample_at_clock(bit_traces, clock_trace, clock_edge)
269
+ else:
270
+ # Sample every point (could be optimized with interval sampling)
271
+ _trace_length = len(next(iter(bit_traces.values())))
272
+ return self.sample_at_intervals(bit_traces, interval_samples=1)
273
+
274
+ def decode_parallel(
275
+ self,
276
+ channels: list[NDArray[np.uint8]],
277
+ ) -> list[int]:
278
+ """Decode parallel bus values from channel list.
279
+
280
+ Simplified interface for parallel bus decoding without clock.
281
+
282
+ Args:
283
+ channels: List of channel data arrays, indexed by bit position.
284
+
285
+ Returns:
286
+ List of decoded integer values (one per sample).
287
+
288
+ Example:
289
+ >>> channels = [ch0, ch1, ch2, ch3] # 4-bit bus
290
+ >>> values = decoder.decode_parallel(channels)
291
+ """
292
+ if not channels:
293
+ return []
294
+
295
+ trace_length = len(channels[0])
296
+ width = len(channels)
297
+ bit_order = self.config.bit_order
298
+
299
+ values = []
300
+ for sample_idx in range(trace_length):
301
+ value = 0
302
+ for bit_idx in range(width):
303
+ bit_val = int(bool(channels[bit_idx][sample_idx]))
304
+ if self.config.active_low:
305
+ bit_val = 1 - bit_val
306
+
307
+ if bit_order == "lsb_first":
308
+ if bit_val:
309
+ value |= 1 << bit_idx
310
+ else: # msb_first
311
+ if bit_val:
312
+ value |= 1 << (width - 1 - bit_idx)
313
+
314
+ values.append(value)
315
+
316
+ return values
317
+
318
+ def decode_with_clock(
319
+ self,
320
+ channels: list[NDArray[np.uint8]],
321
+ clock: NDArray[np.uint8],
322
+ edge: Literal["rising", "falling"] = "rising",
323
+ ) -> list[int]:
324
+ """Decode parallel bus values at clock edges.
325
+
326
+ Args:
327
+ channels: List of channel data arrays, indexed by bit position.
328
+ clock: Clock signal trace (boolean or 0/1).
329
+ edge: Which edge to sample on ('rising' or 'falling').
330
+
331
+ Returns:
332
+ List of decoded integer values (one per clock edge).
333
+
334
+ Example:
335
+ >>> values = decoder.decode_with_clock(channels, clock, 'rising')
336
+ """
337
+ if not channels:
338
+ return []
339
+
340
+ # Convert clock to boolean
341
+ clock_bool = np.asarray(clock, dtype=bool)
342
+
343
+ # Find edges
344
+ if edge == "rising":
345
+ edges = np.where(np.diff(clock_bool.astype(int)) > 0)[0] + 1
346
+ else:
347
+ edges = np.where(np.diff(clock_bool.astype(int)) < 0)[0] + 1
348
+
349
+ width = len(channels)
350
+ bit_order = self.config.bit_order
351
+
352
+ values = []
353
+ for edge_idx in edges:
354
+ value = 0
355
+ for bit_idx in range(width):
356
+ if edge_idx < len(channels[bit_idx]):
357
+ bit_val = int(bool(channels[bit_idx][edge_idx]))
358
+ else:
359
+ bit_val = 0
360
+
361
+ if self.config.active_low:
362
+ bit_val = 1 - bit_val
363
+
364
+ if bit_order == "lsb_first":
365
+ if bit_val:
366
+ value |= 1 << bit_idx
367
+ else: # msb_first
368
+ if bit_val:
369
+ value |= 1 << (width - 1 - bit_idx)
370
+
371
+ values.append(value)
372
+
373
+ return values
374
+
375
+ def decode_transactions(
376
+ self,
377
+ address_channels: list[NDArray[np.uint8]],
378
+ data_channels: list[NDArray[np.uint8]],
379
+ clock: NDArray[np.uint8],
380
+ edge: Literal["rising", "falling"] = "rising",
381
+ ) -> list[dict[str, int]]:
382
+ """Decode bus transactions with address and data.
383
+
384
+ Args:
385
+ address_channels: List of address channel data arrays.
386
+ data_channels: List of data channel data arrays.
387
+ clock: Clock signal trace.
388
+ edge: Which clock edge to sample on.
389
+
390
+ Returns:
391
+ List of transaction dictionaries with 'address' and 'data' keys.
392
+
393
+ Example:
394
+ >>> transactions = decoder.decode_transactions(
395
+ ... address_channels=addr_ch,
396
+ ... data_channels=data_ch,
397
+ ... clock=clk
398
+ ... )
399
+ """
400
+ # Convert clock to boolean
401
+ clock_bool = np.asarray(clock, dtype=bool)
402
+
403
+ # Find edges
404
+ if edge == "rising":
405
+ edges = np.where(np.diff(clock_bool.astype(int)) > 0)[0] + 1
406
+ else:
407
+ edges = np.where(np.diff(clock_bool.astype(int)) < 0)[0] + 1
408
+
409
+ addr_width = len(address_channels)
410
+ data_width = len(data_channels)
411
+ bit_order = self.config.bit_order
412
+
413
+ transactions = []
414
+ for edge_idx in edges:
415
+ # Decode address
416
+ address = 0
417
+ for bit_idx in range(addr_width):
418
+ if edge_idx < len(address_channels[bit_idx]):
419
+ bit_val = int(bool(address_channels[bit_idx][edge_idx]))
420
+ else:
421
+ bit_val = 0
422
+
423
+ if bit_order == "lsb_first":
424
+ if bit_val:
425
+ address |= 1 << bit_idx
426
+ else:
427
+ if bit_val:
428
+ address |= 1 << (addr_width - 1 - bit_idx)
429
+
430
+ # Decode data
431
+ data = 0
432
+ for bit_idx in range(data_width):
433
+ if edge_idx < len(data_channels[bit_idx]):
434
+ bit_val = int(bool(data_channels[bit_idx][edge_idx]))
435
+ else:
436
+ bit_val = 0
437
+
438
+ if bit_order == "lsb_first":
439
+ if bit_val:
440
+ data |= 1 << bit_idx
441
+ else:
442
+ if bit_val:
443
+ data |= 1 << (data_width - 1 - bit_idx)
444
+
445
+ transactions.append(
446
+ {
447
+ "address": address,
448
+ "data": data,
449
+ "sample_index": int(edge_idx),
450
+ }
451
+ )
452
+
453
+ return transactions
454
+
455
+ def sample_at_clock(
456
+ self,
457
+ bit_traces: dict[int, NDArray[np.uint8]],
458
+ clock_trace: NDArray[np.uint8],
459
+ edge: Literal["rising", "falling"] = "rising",
460
+ ) -> list[BusTransaction]:
461
+ """Sample bus at clock edges.
462
+
463
+ Args:
464
+ bit_traces: Dictionary mapping channel index to trace data.
465
+ clock_trace: Clock signal trace (boolean or 0/1).
466
+ edge: Which edge to sample on ('rising' or 'falling').
467
+
468
+ Returns:
469
+ List of BusTransaction objects sampled at clock edges.
470
+
471
+ Example:
472
+ >>> clock = np.array([0,1,0,1,0,1], dtype=bool)
473
+ >>> transactions = decoder.sample_at_clock(bit_traces, clock, 'rising')
474
+ """
475
+ # Convert clock to boolean
476
+ clock_bool = np.asarray(clock_trace, dtype=bool)
477
+
478
+ # Find edges
479
+ if edge == "rising":
480
+ # Rising edge: 0->1 transition
481
+ edges = np.where(np.diff(clock_bool.astype(int)) > 0)[0] + 1
482
+ else:
483
+ # Falling edge: 1->0 transition
484
+ edges = np.where(np.diff(clock_bool.astype(int)) < 0)[0] + 1
485
+
486
+ transactions = []
487
+
488
+ for edge_idx in edges:
489
+ # Sample all bits at this edge
490
+ bit_values = []
491
+ for bit_def in self.config.bits:
492
+ channel = bit_def.get("channel", bit_def.get("bit", 0))
493
+ if channel in bit_traces:
494
+ trace = bit_traces[channel]
495
+ if edge_idx < len(trace):
496
+ bit_val = int(bool(trace[edge_idx]))
497
+ bit_values.append(bit_val)
498
+ else:
499
+ bit_values.append(0)
500
+ else:
501
+ bit_values.append(0)
502
+
503
+ # Apply active-low inversion if needed
504
+ if self.config.active_low:
505
+ bit_values = self._apply_active_low(bit_values)
506
+
507
+ # Reconstruct bus value
508
+ value = self._reconstruct_value(bit_values)
509
+
510
+ # Create transaction
511
+ transaction = BusTransaction(
512
+ timestamp=edge_idx * self._time_base,
513
+ sample_index=int(edge_idx),
514
+ value=value,
515
+ raw_bits=bit_values,
516
+ )
517
+ transactions.append(transaction)
518
+
519
+ return transactions
520
+
521
+ def sample_at_intervals(
522
+ self, bit_traces: dict[int, NDArray[np.uint8]], interval_samples: int
523
+ ) -> list[BusTransaction]:
524
+ """Sample bus at regular intervals.
525
+
526
+ Args:
527
+ bit_traces: Dictionary mapping channel index to trace data.
528
+ interval_samples: Number of samples between each bus sample.
529
+
530
+ Returns:
531
+ List of BusTransaction objects sampled at intervals.
532
+
533
+ Raises:
534
+ ValueError: If interval_samples is not positive.
535
+
536
+ Example:
537
+ >>> transactions = decoder.sample_at_intervals(bit_traces, interval_samples=10)
538
+ """
539
+ if interval_samples <= 0:
540
+ raise ValueError(f"interval_samples must be positive, got {interval_samples}")
541
+
542
+ # Determine trace length
543
+ trace_length = len(next(iter(bit_traces.values())))
544
+
545
+ transactions = []
546
+
547
+ for sample_idx in range(0, trace_length, interval_samples):
548
+ # Sample all bits at this index
549
+ bit_values = []
550
+ for bit_def in self.config.bits:
551
+ channel = bit_def.get("channel", bit_def.get("bit", 0))
552
+ if channel in bit_traces:
553
+ trace = bit_traces[channel]
554
+ if sample_idx < len(trace):
555
+ bit_val = int(bool(trace[sample_idx]))
556
+ bit_values.append(bit_val)
557
+ else:
558
+ bit_values.append(0)
559
+ else:
560
+ bit_values.append(0)
561
+
562
+ # Apply active-low inversion if needed
563
+ if self.config.active_low:
564
+ bit_values = self._apply_active_low(bit_values)
565
+
566
+ # Reconstruct bus value
567
+ value = self._reconstruct_value(bit_values)
568
+
569
+ # Create transaction
570
+ transaction = BusTransaction(
571
+ timestamp=sample_idx * self._time_base,
572
+ sample_index=sample_idx,
573
+ value=value,
574
+ raw_bits=bit_values,
575
+ )
576
+ transactions.append(transaction)
577
+
578
+ return transactions
579
+
580
+ def _reconstruct_value(self, bit_values: list[int]) -> int:
581
+ """Reconstruct bus value from individual bits.
582
+
583
+ Args:
584
+ bit_values: List of bit values (0 or 1) in config order.
585
+
586
+ Returns:
587
+ Integer value reconstructed from bits.
588
+ """
589
+ if not bit_values:
590
+ return 0
591
+
592
+ value = 0
593
+
594
+ if self.config.bit_order == "lsb_first":
595
+ # LSB is first in list, MSB is last
596
+ for i, bit_val in enumerate(bit_values):
597
+ if bit_val:
598
+ value |= 1 << i
599
+ else: # msb_first
600
+ # MSB is first in list, LSB is last
601
+ n_bits = len(bit_values)
602
+ for i, bit_val in enumerate(bit_values):
603
+ if bit_val:
604
+ value |= 1 << (n_bits - 1 - i)
605
+
606
+ return value
607
+
608
+ def _apply_active_low(self, bit_values: list[int]) -> list[int]:
609
+ """Apply active-low inversion if configured.
610
+
611
+ Args:
612
+ bit_values: List of bit values (0 or 1).
613
+
614
+ Returns:
615
+ Inverted bit values if active_low is True, otherwise unchanged.
616
+ """
617
+ if self.config.active_low:
618
+ return [1 - bit for bit in bit_values]
619
+ return bit_values
620
+
621
+
622
+ # Convenience functions
623
+
624
+
625
+ def decode_bus(
626
+ bit_traces: dict[int, NDArray[np.uint8]],
627
+ config: BusConfig | str | Path,
628
+ sample_rate: float,
629
+ clock_trace: NDArray[np.uint8] | None = None,
630
+ clock_edge: Literal["rising", "falling"] = "rising",
631
+ ) -> list[BusTransaction]:
632
+ """Decode bus from bit traces.
633
+
634
+ Convenience function for quick bus decoding without creating a decoder instance.
635
+
636
+ Args:
637
+ bit_traces: Dictionary mapping channel index to trace data.
638
+ config: BusConfig instance or path to YAML config file.
639
+ sample_rate: Sample rate of traces in Hz.
640
+ clock_trace: Optional clock signal for synchronous sampling.
641
+ clock_edge: Which clock edge to sample on.
642
+
643
+ Returns:
644
+ List of BusTransaction objects.
645
+
646
+ Example:
647
+ >>> transactions = decode_bus(bit_traces, 'bus_config.yaml', 100e6)
648
+ """
649
+ if isinstance(config, str | Path):
650
+ config = BusConfig.from_yaml(config)
651
+
652
+ decoder = BusDecoder(config, sample_rate)
653
+ return decoder.decode_bus(bit_traces, clock_trace, clock_edge)
654
+
655
+
656
+ def sample_at_clock(
657
+ bit_traces: dict[int, NDArray[np.uint8]],
658
+ clock_trace: NDArray[np.uint8],
659
+ config: BusConfig,
660
+ sample_rate: float,
661
+ edge: Literal["rising", "falling"] = "rising",
662
+ ) -> list[BusTransaction]:
663
+ """Sample bus at clock edges.
664
+
665
+ Convenience function for clock-based bus sampling.
666
+
667
+ Args:
668
+ bit_traces: Dictionary mapping channel index to trace data.
669
+ clock_trace: Clock signal trace.
670
+ config: Bus configuration.
671
+ sample_rate: Sample rate of traces in Hz.
672
+ edge: Which clock edge to sample on.
673
+
674
+ Returns:
675
+ List of BusTransaction objects.
676
+
677
+ Example:
678
+ >>> transactions = sample_at_clock(bit_traces, clock, config, 100e6, 'rising')
679
+ """
680
+ decoder = BusDecoder(config, sample_rate)
681
+ return decoder.sample_at_clock(bit_traces, clock_trace, edge)
682
+
683
+
684
+ __all__ = [
685
+ "BusConfig",
686
+ "BusDecoder",
687
+ "BusTransaction",
688
+ "ParallelBusConfig",
689
+ "decode_bus",
690
+ "sample_at_clock",
691
+ ]