oscura 0.5.0__py3-none-any.whl → 0.5.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 +1 -1
- oscura/analyzers/digital/__init__.py +0 -48
- oscura/analyzers/digital/extraction.py +0 -195
- oscura/analyzers/protocols/__init__.py +1 -22
- oscura/automotive/__init__.py +1 -1
- oscura/automotive/dtc/data.json +2763 -0
- oscura/export/__init__.py +0 -12
- oscura/export/wireshark/README.md +15 -15
- oscura/exporters/json_export.py +0 -47
- oscura/inference/active_learning/README.md +7 -7
- oscura/pipeline/composition.py +10 -2
- oscura/reporting/__init__.py +0 -7
- oscura/reporting/templates/index.md +13 -13
- 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/utils/autodetect.py +1 -5
- oscura-0.5.1.dist-info/METADATA +583 -0
- {oscura-0.5.0.dist-info → oscura-0.5.1.dist-info}/RECORD +23 -28
- oscura/analyzers/digital/ic_database.py +0 -498
- oscura/analyzers/digital/timing_paths.py +0 -339
- oscura/analyzers/digital/vintage.py +0 -377
- oscura/analyzers/digital/vintage_result.py +0 -148
- oscura/analyzers/protocols/parallel_bus.py +0 -449
- oscura/export/wavedrom.py +0 -430
- oscura/exporters/vintage_logic_csv.py +0 -247
- oscura/reporting/vintage_logic_report.py +0 -523
- oscura/visualization/digital_advanced.py +0 -718
- oscura/visualization/figure_manager.py +0 -156
- oscura-0.5.0.dist-info/METADATA +0 -407
- {oscura-0.5.0.dist-info → oscura-0.5.1.dist-info}/WHEEL +0 -0
- {oscura-0.5.0.dist-info → oscura-0.5.1.dist-info}/entry_points.txt +0 -0
- {oscura-0.5.0.dist-info → oscura-0.5.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,449 +0,0 @@
|
|
|
1
|
-
"""Parallel bus protocol decoders for vintage systems.
|
|
2
|
-
|
|
3
|
-
Implements decoders for classic parallel bus protocols:
|
|
4
|
-
- IEEE-488 (GPIB): General Purpose Interface Bus for instruments
|
|
5
|
-
- Centronics: Parallel printer interface
|
|
6
|
-
- ISA: Industry Standard Architecture bus
|
|
7
|
-
|
|
8
|
-
Example:
|
|
9
|
-
>>> from oscura.analyzers.protocols.parallel_bus import decode_gpib, decode_centronics
|
|
10
|
-
>>> frames = decode_gpib(dio_lines, dav, nrfd, ndac, eoi, atn)
|
|
11
|
-
>>> print_data = decode_centronics(data_lines, strobe, busy, ack)
|
|
12
|
-
"""
|
|
13
|
-
|
|
14
|
-
from __future__ import annotations
|
|
15
|
-
|
|
16
|
-
from dataclasses import dataclass
|
|
17
|
-
from enum import Enum
|
|
18
|
-
from typing import TYPE_CHECKING
|
|
19
|
-
|
|
20
|
-
import numpy as np
|
|
21
|
-
|
|
22
|
-
if TYPE_CHECKING:
|
|
23
|
-
from numpy.typing import NDArray
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
# =============================================================================
|
|
27
|
-
# IEEE-488 (GPIB) Protocol Decoder
|
|
28
|
-
# =============================================================================
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
class GPIBMessageType(Enum):
|
|
32
|
-
"""GPIB message types."""
|
|
33
|
-
|
|
34
|
-
DATA = "data" # Data bytes (ATN=0)
|
|
35
|
-
COMMAND = "command" # Command bytes (ATN=1)
|
|
36
|
-
TALK_ADDRESS = "talk_address" # Talk address
|
|
37
|
-
LISTEN_ADDRESS = "listen_address" # Listen address
|
|
38
|
-
SECONDARY_ADDRESS = "secondary_address" # Secondary address
|
|
39
|
-
UNIVERSAL_COMMAND = "universal_command" # Universal command
|
|
40
|
-
ADDRESSED_COMMAND = "addressed_command" # Addressed command
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
@dataclass
|
|
44
|
-
class GPIBFrame:
|
|
45
|
-
"""A decoded GPIB frame.
|
|
46
|
-
|
|
47
|
-
Attributes:
|
|
48
|
-
timestamp: Frame timestamp in seconds.
|
|
49
|
-
data: Data byte value (0-255).
|
|
50
|
-
message_type: Type of GPIB message.
|
|
51
|
-
eoi: End-Or-Identify asserted.
|
|
52
|
-
description: Human-readable description.
|
|
53
|
-
"""
|
|
54
|
-
|
|
55
|
-
timestamp: float
|
|
56
|
-
data: int
|
|
57
|
-
message_type: GPIBMessageType
|
|
58
|
-
eoi: bool
|
|
59
|
-
description: str
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
def decode_gpib(
|
|
63
|
-
dio_lines: list[NDArray[np.bool_]], # DIO1-DIO8 (8 data lines)
|
|
64
|
-
dav: NDArray[np.bool_], # Data Valid
|
|
65
|
-
nrfd: NDArray[np.bool_], # Not Ready For Data
|
|
66
|
-
ndac: NDArray[np.bool_], # Not Data Accepted
|
|
67
|
-
eoi: NDArray[np.bool_], # End Or Identify
|
|
68
|
-
atn: NDArray[np.bool_], # Attention
|
|
69
|
-
sample_rate: float = 1.0,
|
|
70
|
-
) -> list[GPIBFrame]:
|
|
71
|
-
"""Decode IEEE-488 (GPIB) bus transactions.
|
|
72
|
-
|
|
73
|
-
Args:
|
|
74
|
-
dio_lines: List of 8 digital traces for DIO1-DIO8.
|
|
75
|
-
dav: Data Valid signal (active low).
|
|
76
|
-
nrfd: Not Ready For Data signal (active low).
|
|
77
|
-
ndac: Not Data Accepted signal (active low).
|
|
78
|
-
eoi: End Or Identify signal (active low).
|
|
79
|
-
atn: Attention signal (active low).
|
|
80
|
-
sample_rate: Sample rate in Hz.
|
|
81
|
-
|
|
82
|
-
Returns:
|
|
83
|
-
List of GPIBFrame objects.
|
|
84
|
-
|
|
85
|
-
Example:
|
|
86
|
-
>>> frames = decode_gpib(dio, dav, nrfd, ndac, eoi, atn, 1e6)
|
|
87
|
-
>>> for frame in frames:
|
|
88
|
-
... print(f"{frame.timestamp*1e6:.1f}us: {frame.description}")
|
|
89
|
-
"""
|
|
90
|
-
if len(dio_lines) != 8:
|
|
91
|
-
raise ValueError("GPIB requires exactly 8 DIO lines")
|
|
92
|
-
|
|
93
|
-
frames: list[GPIBFrame] = []
|
|
94
|
-
time_base = 1.0 / sample_rate
|
|
95
|
-
|
|
96
|
-
# Combine DIO lines into data bus
|
|
97
|
-
data_bus = np.zeros(len(dio_lines[0]), dtype=np.uint8)
|
|
98
|
-
for i, line in enumerate(dio_lines):
|
|
99
|
-
data_bus |= (line.astype(np.uint8) << i).astype(np.uint8)
|
|
100
|
-
|
|
101
|
-
# Detect DAV falling edges (data valid)
|
|
102
|
-
dav_falling = np.where(np.diff(dav.astype(np.int8)) == -1)[0]
|
|
103
|
-
|
|
104
|
-
for idx in dav_falling:
|
|
105
|
-
# Sample data after falling edge
|
|
106
|
-
sample_idx = idx + 1
|
|
107
|
-
if sample_idx >= len(data_bus):
|
|
108
|
-
continue
|
|
109
|
-
|
|
110
|
-
timestamp = idx * time_base
|
|
111
|
-
data_byte = int(data_bus[sample_idx])
|
|
112
|
-
eoi_active = not eoi[sample_idx] # Active low
|
|
113
|
-
atn_active = not atn[sample_idx] # Active low
|
|
114
|
-
|
|
115
|
-
# Decode message type based on ATN and data
|
|
116
|
-
if atn_active:
|
|
117
|
-
# Check address bytes first (bit patterns for talk/listen)
|
|
118
|
-
if data_byte & 0x40:
|
|
119
|
-
# Talk address (bit 6 set)
|
|
120
|
-
address = data_byte & 0x1F
|
|
121
|
-
msg_type = GPIBMessageType.TALK_ADDRESS
|
|
122
|
-
desc = f"Talk address {address}"
|
|
123
|
-
elif data_byte & 0x20:
|
|
124
|
-
# Listen address (bit 5 set, bit 6 clear)
|
|
125
|
-
address = data_byte & 0x1F
|
|
126
|
-
msg_type = GPIBMessageType.LISTEN_ADDRESS
|
|
127
|
-
desc = f"Listen address {address}"
|
|
128
|
-
elif 0x10 <= data_byte <= 0x1F:
|
|
129
|
-
# Universal commands
|
|
130
|
-
msg_type = GPIBMessageType.UNIVERSAL_COMMAND
|
|
131
|
-
desc = _gpib_universal_command_name(data_byte)
|
|
132
|
-
else:
|
|
133
|
-
# Addressed commands (0x00-0x0F and others)
|
|
134
|
-
msg_type = GPIBMessageType.ADDRESSED_COMMAND
|
|
135
|
-
desc = _gpib_addressed_command_name(data_byte)
|
|
136
|
-
else:
|
|
137
|
-
# Data byte
|
|
138
|
-
msg_type = GPIBMessageType.DATA
|
|
139
|
-
desc = f"Data: 0x{data_byte:02X}"
|
|
140
|
-
if 32 <= data_byte <= 126:
|
|
141
|
-
desc += f" ('{chr(data_byte)}')"
|
|
142
|
-
|
|
143
|
-
if eoi_active:
|
|
144
|
-
desc += " [EOI]"
|
|
145
|
-
|
|
146
|
-
frames.append(
|
|
147
|
-
GPIBFrame(
|
|
148
|
-
timestamp=timestamp,
|
|
149
|
-
data=data_byte,
|
|
150
|
-
message_type=msg_type,
|
|
151
|
-
eoi=eoi_active,
|
|
152
|
-
description=desc,
|
|
153
|
-
)
|
|
154
|
-
)
|
|
155
|
-
|
|
156
|
-
return frames
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
def _gpib_universal_command_name(cmd: int) -> str:
|
|
160
|
-
"""Get name of GPIB universal command."""
|
|
161
|
-
commands = {
|
|
162
|
-
0x11: "DCL (Device Clear)",
|
|
163
|
-
0x14: "GET (Group Execute Trigger)",
|
|
164
|
-
0x15: "GTL (Go To Local)",
|
|
165
|
-
0x08: "LLO (Local Lockout)",
|
|
166
|
-
0x01: "SPD (Serial Poll Disable)",
|
|
167
|
-
0x18: "SPE (Serial Poll Enable)",
|
|
168
|
-
0x13: "PPU (Parallel Poll Unconfigure)",
|
|
169
|
-
}
|
|
170
|
-
return commands.get(cmd, f"Unknown universal command 0x{cmd:02X}")
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
def _gpib_addressed_command_name(cmd: int) -> str:
|
|
174
|
-
"""Get name of GPIB addressed command."""
|
|
175
|
-
commands = {
|
|
176
|
-
0x04: "SDC (Selected Device Clear)",
|
|
177
|
-
0x05: "PPC (Parallel Poll Configure)",
|
|
178
|
-
0x09: "TCT (Take Control)",
|
|
179
|
-
}
|
|
180
|
-
return commands.get(cmd, f"Unknown addressed command 0x{cmd:02X}")
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
# =============================================================================
|
|
184
|
-
# Centronics Parallel Printer Protocol Decoder
|
|
185
|
-
# =============================================================================
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
@dataclass
|
|
189
|
-
class CentronicsFrame:
|
|
190
|
-
"""A decoded Centronics printer frame.
|
|
191
|
-
|
|
192
|
-
Attributes:
|
|
193
|
-
timestamp: Frame timestamp in seconds.
|
|
194
|
-
data: Data byte value (0-255).
|
|
195
|
-
character: ASCII character (if printable).
|
|
196
|
-
control: Control signal states.
|
|
197
|
-
"""
|
|
198
|
-
|
|
199
|
-
timestamp: float
|
|
200
|
-
data: int
|
|
201
|
-
character: str | None
|
|
202
|
-
control: dict[str, bool]
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
def decode_centronics(
|
|
206
|
-
data_lines: list[NDArray[np.bool_]], # D0-D7 (8 data lines)
|
|
207
|
-
strobe: NDArray[np.bool_], # Strobe signal (active low)
|
|
208
|
-
busy: NDArray[np.bool_], # Busy signal
|
|
209
|
-
ack: NDArray[np.bool_], # Acknowledge signal (active low)
|
|
210
|
-
sample_rate: float = 1.0,
|
|
211
|
-
*,
|
|
212
|
-
select: NDArray[np.bool_] | None = None, # Select signal
|
|
213
|
-
paper_out: NDArray[np.bool_] | None = None, # Paper Out signal
|
|
214
|
-
error: NDArray[np.bool_] | None = None, # Error signal
|
|
215
|
-
) -> list[CentronicsFrame]:
|
|
216
|
-
"""Decode Centronics parallel printer protocol.
|
|
217
|
-
|
|
218
|
-
Args:
|
|
219
|
-
data_lines: List of 8 digital traces for D0-D7.
|
|
220
|
-
strobe: Strobe signal (active low).
|
|
221
|
-
busy: Busy signal (high when printer busy).
|
|
222
|
-
ack: Acknowledge signal (active low pulse).
|
|
223
|
-
sample_rate: Sample rate in Hz.
|
|
224
|
-
select: Optional select signal.
|
|
225
|
-
paper_out: Optional paper out signal.
|
|
226
|
-
error: Optional error signal.
|
|
227
|
-
|
|
228
|
-
Returns:
|
|
229
|
-
List of CentronicsFrame objects.
|
|
230
|
-
|
|
231
|
-
Example:
|
|
232
|
-
>>> frames = decode_centronics(data, strobe, busy, ack, 1e6)
|
|
233
|
-
>>> for frame in frames:
|
|
234
|
-
... if frame.character:
|
|
235
|
-
... print(frame.character, end='')
|
|
236
|
-
"""
|
|
237
|
-
if len(data_lines) != 8:
|
|
238
|
-
raise ValueError("Centronics requires exactly 8 data lines")
|
|
239
|
-
|
|
240
|
-
frames: list[CentronicsFrame] = []
|
|
241
|
-
time_base = 1.0 / sample_rate
|
|
242
|
-
|
|
243
|
-
# Combine data lines into bytes
|
|
244
|
-
data_bus = np.zeros(len(data_lines[0]), dtype=np.uint8)
|
|
245
|
-
for i, line in enumerate(data_lines):
|
|
246
|
-
data_bus |= (line.astype(np.uint8) << i).astype(np.uint8)
|
|
247
|
-
|
|
248
|
-
# Detect STROBE falling edges (data latch)
|
|
249
|
-
strobe_falling = np.where(np.diff(strobe.astype(np.int8)) == -1)[0]
|
|
250
|
-
|
|
251
|
-
for idx in strobe_falling:
|
|
252
|
-
# Sample data after falling edge
|
|
253
|
-
sample_idx = idx + 1
|
|
254
|
-
if sample_idx >= len(data_bus):
|
|
255
|
-
continue
|
|
256
|
-
|
|
257
|
-
timestamp = idx * time_base
|
|
258
|
-
data_byte = int(data_bus[sample_idx])
|
|
259
|
-
|
|
260
|
-
# Check if printable ASCII
|
|
261
|
-
char = chr(data_byte) if 32 <= data_byte <= 126 else None
|
|
262
|
-
|
|
263
|
-
# Capture control signals
|
|
264
|
-
control = {
|
|
265
|
-
"busy": bool(busy[sample_idx]),
|
|
266
|
-
"ack": not ack[sample_idx], # Active low
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
if select is not None:
|
|
270
|
-
control["select"] = bool(select[sample_idx])
|
|
271
|
-
if paper_out is not None:
|
|
272
|
-
control["paper_out"] = bool(paper_out[sample_idx])
|
|
273
|
-
if error is not None:
|
|
274
|
-
control["error"] = bool(error[sample_idx])
|
|
275
|
-
|
|
276
|
-
frames.append(
|
|
277
|
-
CentronicsFrame(
|
|
278
|
-
timestamp=timestamp,
|
|
279
|
-
data=data_byte,
|
|
280
|
-
character=char,
|
|
281
|
-
control=control,
|
|
282
|
-
)
|
|
283
|
-
)
|
|
284
|
-
|
|
285
|
-
return frames
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
# =============================================================================
|
|
289
|
-
# ISA Bus Protocol Analyzer
|
|
290
|
-
# =============================================================================
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
class ISACycleType(Enum):
|
|
294
|
-
"""ISA bus cycle types."""
|
|
295
|
-
|
|
296
|
-
MEMORY_READ = "memory_read"
|
|
297
|
-
MEMORY_WRITE = "memory_write"
|
|
298
|
-
IO_READ = "io_read"
|
|
299
|
-
IO_WRITE = "io_write"
|
|
300
|
-
DMA = "dma"
|
|
301
|
-
INTERRUPT = "interrupt"
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
@dataclass
|
|
305
|
-
class ISATransaction:
|
|
306
|
-
"""An ISA bus transaction.
|
|
307
|
-
|
|
308
|
-
Attributes:
|
|
309
|
-
timestamp: Transaction timestamp in seconds.
|
|
310
|
-
cycle_type: Type of bus cycle.
|
|
311
|
-
address: Address (20-bit for memory, 16-bit for I/O).
|
|
312
|
-
data: Data byte (if applicable).
|
|
313
|
-
description: Human-readable description.
|
|
314
|
-
"""
|
|
315
|
-
|
|
316
|
-
timestamp: float
|
|
317
|
-
cycle_type: ISACycleType
|
|
318
|
-
address: int
|
|
319
|
-
data: int | None
|
|
320
|
-
description: str
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
def decode_isa_bus(
|
|
324
|
-
address_lines: list[NDArray[np.bool_]], # SA0-SA19 (20 address lines)
|
|
325
|
-
data_lines: list[NDArray[np.bool_]], # SD0-SD7 (8 data lines)
|
|
326
|
-
ior: NDArray[np.bool_], # I/O Read (active low)
|
|
327
|
-
iow: NDArray[np.bool_], # I/O Write (active low)
|
|
328
|
-
memr: NDArray[np.bool_], # Memory Read (active low)
|
|
329
|
-
memw: NDArray[np.bool_], # Memory Write (active low)
|
|
330
|
-
ale: NDArray[np.bool_], # Address Latch Enable
|
|
331
|
-
sample_rate: float = 1.0,
|
|
332
|
-
) -> list[ISATransaction]:
|
|
333
|
-
"""Decode ISA bus transactions.
|
|
334
|
-
|
|
335
|
-
Args:
|
|
336
|
-
address_lines: List of 20 digital traces for SA0-SA19.
|
|
337
|
-
data_lines: List of 8 digital traces for SD0-SD7.
|
|
338
|
-
ior: I/O Read signal (active low).
|
|
339
|
-
iow: I/O Write signal (active low).
|
|
340
|
-
memr: Memory Read signal (active low).
|
|
341
|
-
memw: Memory Write signal (active low).
|
|
342
|
-
ale: Address Latch Enable.
|
|
343
|
-
sample_rate: Sample rate in Hz.
|
|
344
|
-
|
|
345
|
-
Returns:
|
|
346
|
-
List of ISATransaction objects.
|
|
347
|
-
|
|
348
|
-
Example:
|
|
349
|
-
>>> trans = decode_isa_bus(addr, data, ior, iow, memr, memw, ale, 1e6)
|
|
350
|
-
>>> for t in trans:
|
|
351
|
-
... print(f"{t.timestamp*1e6:.1f}us: {t.description}")
|
|
352
|
-
"""
|
|
353
|
-
if len(address_lines) < 16:
|
|
354
|
-
raise ValueError("ISA bus requires at least 16 address lines")
|
|
355
|
-
if len(data_lines) != 8:
|
|
356
|
-
raise ValueError("ISA bus requires exactly 8 data lines")
|
|
357
|
-
|
|
358
|
-
transactions: list[ISATransaction] = []
|
|
359
|
-
time_base = 1.0 / sample_rate
|
|
360
|
-
|
|
361
|
-
# Combine address lines
|
|
362
|
-
address_bus = np.zeros(len(address_lines[0]), dtype=np.uint32)
|
|
363
|
-
for i, line in enumerate(address_lines):
|
|
364
|
-
address_bus |= (line.astype(np.uint32) << i).astype(np.uint32)
|
|
365
|
-
|
|
366
|
-
# Combine data lines
|
|
367
|
-
data_bus = np.zeros(len(data_lines[0]), dtype=np.uint8)
|
|
368
|
-
for i, line in enumerate(data_lines):
|
|
369
|
-
data_bus |= (line.astype(np.uint8) << i).astype(np.uint8)
|
|
370
|
-
|
|
371
|
-
# Detect ALE falling edges (address latch)
|
|
372
|
-
ale_falling = np.where(np.diff(ale.astype(np.int8)) == -1)[0]
|
|
373
|
-
|
|
374
|
-
for idx in ale_falling:
|
|
375
|
-
if idx >= len(address_bus):
|
|
376
|
-
continue
|
|
377
|
-
|
|
378
|
-
timestamp = idx * time_base
|
|
379
|
-
address = int(address_bus[idx])
|
|
380
|
-
|
|
381
|
-
# Look ahead for read/write strobes (larger window to catch delayed strobes)
|
|
382
|
-
search_window = min(idx + 200, len(ior))
|
|
383
|
-
|
|
384
|
-
ior_active = np.any(~ior[idx:search_window])
|
|
385
|
-
iow_active = np.any(~iow[idx:search_window])
|
|
386
|
-
memr_active = np.any(~memr[idx:search_window])
|
|
387
|
-
memw_active = np.any(~memw[idx:search_window])
|
|
388
|
-
|
|
389
|
-
# Determine cycle type
|
|
390
|
-
data_val = None
|
|
391
|
-
|
|
392
|
-
if ior_active:
|
|
393
|
-
cycle_type = ISACycleType.IO_READ
|
|
394
|
-
desc = f"I/O Read from 0x{address:04X}"
|
|
395
|
-
# Find data at IOR falling edge
|
|
396
|
-
ior_idx = np.where(~ior[idx:search_window])[0]
|
|
397
|
-
if len(ior_idx) > 0:
|
|
398
|
-
data_val = int(data_bus[idx + ior_idx[0]])
|
|
399
|
-
desc += f" = 0x{data_val:02X}"
|
|
400
|
-
|
|
401
|
-
elif iow_active:
|
|
402
|
-
cycle_type = ISACycleType.IO_WRITE
|
|
403
|
-
iow_idx = np.where(~iow[idx:search_window])[0]
|
|
404
|
-
if len(iow_idx) > 0:
|
|
405
|
-
data_val = int(data_bus[idx + iow_idx[0]])
|
|
406
|
-
desc = f"I/O Write 0x{data_val:02X} to 0x{address:04X}"
|
|
407
|
-
|
|
408
|
-
elif memr_active:
|
|
409
|
-
cycle_type = ISACycleType.MEMORY_READ
|
|
410
|
-
desc = f"Memory Read from 0x{address:05X}"
|
|
411
|
-
memr_idx = np.where(~memr[idx:search_window])[0]
|
|
412
|
-
if len(memr_idx) > 0:
|
|
413
|
-
data_val = int(data_bus[idx + memr_idx[0]])
|
|
414
|
-
desc += f" = 0x{data_val:02X}"
|
|
415
|
-
|
|
416
|
-
elif memw_active:
|
|
417
|
-
cycle_type = ISACycleType.MEMORY_WRITE
|
|
418
|
-
memw_idx = np.where(~memw[idx:search_window])[0]
|
|
419
|
-
if len(memw_idx) > 0:
|
|
420
|
-
data_val = int(data_bus[idx + memw_idx[0]])
|
|
421
|
-
desc = f"Memory Write 0x{data_val:02X} to 0x{address:05X}"
|
|
422
|
-
|
|
423
|
-
else:
|
|
424
|
-
# No control signals active
|
|
425
|
-
continue
|
|
426
|
-
|
|
427
|
-
transactions.append(
|
|
428
|
-
ISATransaction(
|
|
429
|
-
timestamp=timestamp,
|
|
430
|
-
cycle_type=cycle_type,
|
|
431
|
-
address=address,
|
|
432
|
-
data=data_val,
|
|
433
|
-
description=desc,
|
|
434
|
-
)
|
|
435
|
-
)
|
|
436
|
-
|
|
437
|
-
return transactions
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
__all__ = [
|
|
441
|
-
"CentronicsFrame",
|
|
442
|
-
"GPIBFrame",
|
|
443
|
-
"GPIBMessageType",
|
|
444
|
-
"ISACycleType",
|
|
445
|
-
"ISATransaction",
|
|
446
|
-
"decode_centronics",
|
|
447
|
-
"decode_gpib",
|
|
448
|
-
"decode_isa_bus",
|
|
449
|
-
]
|