cocotbext-obi 1.0.0__tar.gz

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 (32) hide show
  1. cocotbext_obi-1.0.0/LICENSE +21 -0
  2. cocotbext_obi-1.0.0/MANIFEST.in +9 -0
  3. cocotbext_obi-1.0.0/PKG-INFO +488 -0
  4. cocotbext_obi-1.0.0/README.md +452 -0
  5. cocotbext_obi-1.0.0/cocotbext/obi/__init__.py +83 -0
  6. cocotbext_obi-1.0.0/cocotbext/obi/address_map.py +124 -0
  7. cocotbext_obi-1.0.0/cocotbext/obi/address_space.py +378 -0
  8. cocotbext_obi-1.0.0/cocotbext/obi/buddy_allocator.py +92 -0
  9. cocotbext_obi-1.0.0/cocotbext/obi/bus.py +187 -0
  10. cocotbext_obi-1.0.0/cocotbext/obi/constants.py +44 -0
  11. cocotbext_obi-1.0.0/cocotbext/obi/memory.py +101 -0
  12. cocotbext_obi-1.0.0/cocotbext/obi/obi_base.py +92 -0
  13. cocotbext_obi-1.0.0/cocotbext/obi/obi_bus.py +71 -0
  14. cocotbext_obi-1.0.0/cocotbext/obi/obi_device.py +225 -0
  15. cocotbext_obi-1.0.0/cocotbext/obi/obi_host.py +561 -0
  16. cocotbext_obi-1.0.0/cocotbext/obi/obi_interface.py +148 -0
  17. cocotbext_obi-1.0.0/cocotbext/obi/obi_master.py +48 -0
  18. cocotbext_obi-1.0.0/cocotbext/obi/obi_monitor.py +167 -0
  19. cocotbext_obi-1.0.0/cocotbext/obi/obi_ram.py +45 -0
  20. cocotbext_obi-1.0.0/cocotbext/obi/obi_slave.py +45 -0
  21. cocotbext_obi-1.0.0/cocotbext/obi/sparse_memory.py +113 -0
  22. cocotbext_obi-1.0.0/cocotbext/obi/utils.py +65 -0
  23. cocotbext_obi-1.0.0/cocotbext/obi/version.py +1 -0
  24. cocotbext_obi-1.0.0/cocotbext_obi.egg-info/PKG-INFO +488 -0
  25. cocotbext_obi-1.0.0/cocotbext_obi.egg-info/SOURCES.txt +31 -0
  26. cocotbext_obi-1.0.0/cocotbext_obi.egg-info/dependency_links.txt +1 -0
  27. cocotbext_obi-1.0.0/cocotbext_obi.egg-info/requires.txt +8 -0
  28. cocotbext_obi-1.0.0/cocotbext_obi.egg-info/top_level.txt +1 -0
  29. cocotbext_obi-1.0.0/requirements.txt +11 -0
  30. cocotbext_obi-1.0.0/setup.cfg +77 -0
  31. cocotbext_obi-1.0.0/setup.py +9 -0
  32. cocotbext_obi-1.0.0/tests/test_format_addr.py +55 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024-2025 Dave Keeshan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,9 @@
1
+ include README.md
2
+ include LICENSE
3
+ include requirements.txt
4
+
5
+
6
+
7
+
8
+
9
+
@@ -0,0 +1,488 @@
1
+ Metadata-Version: 2.4
2
+ Name: cocotbext_obi
3
+ Version: 1.0.0
4
+ Summary: OBI (Open Bus Interface) modules for cocotb
5
+ Home-page: https://github.com/daxzio/cocotbext-obi
6
+ Download-URL: https://github.com/daxzio/cocotbext-obi/tarball/master
7
+ Author: Dave Keeshan
8
+ Author-email: dave.keeshan@daxzio.com
9
+ License: MIT
10
+ Project-URL: Bug Tracker, https://github.com/daxzio/cocotbext-obi/issues
11
+ Project-URL: Source Code, https://github.com/daxzio/cocotbext-obi
12
+ Keywords: obi,cocotb,openhw,risc-v
13
+ Platform: any
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Framework :: cocotb
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Programming Language :: Python :: 3.14
24
+ Classifier: Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)
25
+ Requires-Python: >=3.10
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ Requires-Dist: cocotb>=1.9.0
29
+ Provides-Extra: test
30
+ Requires-Dist: pytest; extra == "test"
31
+ Requires-Dist: pytest-xdist; extra == "test"
32
+ Provides-Extra: interface
33
+ Requires-Dist: cocotbext-interface; extra == "interface"
34
+ Dynamic: download-url
35
+ Dynamic: license-file
36
+
37
+ # OBI interface modules for Cocotb
38
+
39
+ GitHub repository: https://github.com/daxzio/cocotbext-obi
40
+
41
+ ## Introduction
42
+
43
+ OBI (Open Bus Interface) simulation models for [cocotb](https://github.com/cocotb/cocotb).
44
+
45
+ The OBI protocol is defined by the OpenHW Group for use in RISC-V and other open-source processor designs.
46
+
47
+ ## Features
48
+
49
+ - **ObiHost**: Host/manager driver for OBI protocol
50
+ - **ObiBus**: Bus signal container with auto-discovery
51
+ - **Wide data support**: Automatically splits data wider than bus into multiple transactions
52
+ - **Transaction IDs**: Supports pipelined transactions with ID tracking
53
+ - **Multiple outstanding transactions**: Configurable pipeline depth with in-order completion
54
+ - **Error handling**: Full error response validation
55
+ - **Timeout support**: Configurable transaction timeouts
56
+
57
+ ## Installation
58
+
59
+ Installation from pip (when available):
60
+
61
+ $ pip install cocotbext-obi
62
+
63
+ Installation from git (latest development version):
64
+
65
+ $ pip install https://github.com/daxzio/cocotbext-obi/archive/main.zip
66
+
67
+ Installation for active development:
68
+
69
+ $ git clone https://github.com/daxzio/cocotbext-obi
70
+ $ pip install -e cocotbext-obi
71
+
72
+ Optional `ObiInterface` extra (needs **cocotb 2.x**):
73
+
74
+ $ pip install cocotbext-obi[interface]
75
+
76
+ Requires **Python 3.10+**. CI runs Python 3.10–3.13 against cocotb v1.9.2 and
77
+ v2.0.1, and Python 3.10–3.14 against cocotb master.
78
+
79
+ ## OBI Protocol Overview
80
+
81
+ OBI uses a two-phase handshake protocol:
82
+
83
+ **Request Phase (A-Channel):**
84
+ - `req` and `gnt` handshake for address/control transfer
85
+ - Manager asserts `req` with address and control signals
86
+ - Subordinate asserts `gnt` when ready to accept
87
+
88
+ **Response Phase (R-Channel):**
89
+ - `rvalid` and `rready` handshake for data transfer
90
+ - Subordinate asserts `rvalid` with response data
91
+ - Manager asserts `rready` when ready to accept
92
+
93
+ ## Usage Example
94
+
95
+ ### OBI Bus
96
+
97
+ The `ObiBus` is used to map to an OBI interface on the `dut`. Class methods `from_entity` and `from_prefix` are provided to facilitate signal name matching.
98
+
99
+ #### Required Signals:
100
+ * _req_ - Request valid
101
+ * _gnt_ - Grant (ready to accept)
102
+ * _addr_ - Address
103
+ * _we_ - Write enable
104
+ * _be_ - Byte enable
105
+ * _wdata_ - Write data
106
+ * _aid_ - Address/transaction ID
107
+ * _rvalid_ - Response valid
108
+ * _rready_ - Response ready
109
+ * _rdata_ - Read data
110
+ * _err_ - Error flag
111
+ * _rid_ - Response ID
112
+
113
+ ### OBI Host
114
+
115
+ The `ObiHost` class implements an OBI host/manager and is capable of generating read and write operations against OBI devices.
116
+
117
+ The host automatically handles data wider than the bus width by splitting transactions into multiple sequential OBI accesses at consecutive addresses. This allows seamless transfers of wide data values across narrower OBI interfaces.
118
+
119
+ To use these modules, import and connect to the DUT:
120
+
121
+ ```python
122
+ from cocotbext.obi import ObiHost, ObiBus
123
+
124
+ bus = ObiBus.from_prefix(dut, "s_obi")
125
+ obi_driver = ObiHost(bus, dut.clk)
126
+ ```
127
+
128
+ The first argument to the constructor accepts an `ObiBus` object. These objects are containers for the interface signals and include class methods to automate connections.
129
+
130
+ Once the module is instantiated, read and write operations can be initiated:
131
+
132
+ `ObiMaster` is a deprecated subclass of `ObiHost` and remains available for existing testbenches.
133
+
134
+ ```python
135
+ # Write operations
136
+ await obi_driver.write(0x1000, 0x12345678) # Single 32-bit write
137
+ await obi_driver.write(0x2000, 0x123456789ABCDEF0) # Auto-splits to two writes
138
+
139
+ # Read operations
140
+ data = await obi_driver.read(0x1000) # Returns bytes
141
+ value = int.from_bytes(data, 'little')
142
+
143
+ # With data verification
144
+ await obi_driver.read(0x1000, 0x12345678) # Raises exception if mismatch
145
+
146
+ # With error expectation
147
+ await obi_driver.write(0xBAD_ADDR, 0xFF, error_expected=True)
148
+ ```
149
+
150
+ #### `ObiHost` Constructor Parameters
151
+ * _bus_: `ObiBus` object containing OBI interface signals
152
+ * _clock_: Clock signal
153
+ * _timeout_cycles_: Maximum clock cycles to wait before timing out (optional, default `1000`). Set to `-1` to disable timeout.
154
+ * _max_outstanding_: Maximum number of outstanding transactions (optional, default `1`). Set to `2` or higher to enable pipelined transactions.
155
+
156
+ #### Methods
157
+ * `wait()`: Blocking wait until all outstanding operations complete
158
+ * `write(addr, data, strb=-1, error_expected=False, length=-1, device=0, index=-1)`: Write _data_ (bytes or int) to _addr_ (int or register name when `addrmap` is configured), wait for result. If _data_ is wider than the bus width, it will automatically be split into multiple sequential OBI write accesses at consecutive addresses. After completion, `intra_delay` idle clock cycles are inserted (default `0`).
159
+ * `write_nowait(addr, data, strb=-1, error_expected=False, length=-1, device=0, index=-1)`: Write _data_ to _addr_, queue without waiting.
160
+ * `read(addr, data=bytes(), error_expected=False, length=-1, device=0, index=-1)`: Read bytes at _addr_ (int or register name). If _data_ supplied, verify it matches. If _data_ is wider than the bus width, it will automatically be split into multiple sequential OBI read accesses at consecutive addresses. After completion, `intra_delay` idle clock cycles are inserted (default `0`).
161
+ * `read_nowait(addr, data=bytes(), error_expected=False, length=-1, device=0, index=-1)`: Read bytes at _addr_, queue without waiting.
162
+ * `poll(addr, data=bytes(), device=0, index=-1)`: Repeatedly read _addr_ until the returned data equals _data_.
163
+ * `addaddrmap(addrmap, device=0)`: Register a name-to-address map. Preferred over direct assignment because it updates log column alignment.
164
+ * `format_addr(addr, device=0)`: Reverse lookup — return the register name for _addr_, or `0x........` if unmapped.
165
+
166
+ #### Error Handling
167
+
168
+ The `ObiHost` includes exception control for error testing:
169
+
170
+ * `exception_enabled`: When True (default), raises exceptions on unexpected errors. When False, logs warnings and sets `exception_occurred` flag.
171
+ * `exception_occurred`: Boolean flag set when an error occurs unexpectedly.
172
+
173
+ ```python
174
+ # Normal operation - exceptions enabled
175
+ await obi.write(read_only_addr, data, error_expected=True) # OK
176
+
177
+ # For testing error detection without exceptions
178
+ obi.exception_enabled = False
179
+ await obi.write(read_only_addr, data, error_expected=False)
180
+ assert obi.exception_occurred == True # Error was detected
181
+ ```
182
+
183
+ ### OBI Device Models
184
+
185
+ Three device/target models are provided for building self-contained
186
+ testbenches (no RTL DUT required - the host and device BFMs can drive a
187
+ shared set of pins):
188
+
189
+ * **`ObiDevice`** - a responder backed by a memory *target* (any object exposing
190
+ async `read`/`write`, e.g. a `MemoryRegion`). Override `_read`/`_write` for
191
+ custom behaviour.
192
+ * **`ObiRam`** - `ObiDevice` pre-mixed with a sparse in-memory `Memory` store.
193
+ * **`ObiMonitor`** - a passive monitor that records `ObiTransaction` objects and
194
+ can optionally check that bus signals only change on clock edges
195
+ (`enable_check_sync()` / `disable_check_sync()`).
196
+
197
+ `ObiSlave` is a deprecated subclass of `ObiDevice` and remains available for existing testbenches.
198
+
199
+ ```python
200
+ from cocotbext.obi import ObiBus, ObiHost, ObiDevice, ObiRam, MemoryRegion
201
+
202
+ bus = ObiBus.from_prefix(dut, "s_obi")
203
+ host = ObiHost(bus, dut.clk)
204
+
205
+ # Memory-region-backed device
206
+ device = ObiDevice(bus, dut.clk)
207
+ device.target = MemoryRegion(2**device.address_width)
208
+
209
+ # ...or a RAM device in one line
210
+ ram = ObiRam(bus, dut.clk)
211
+ ```
212
+
213
+ `ObiDevice`/`ObiRam` accept `size_bytes=` to size an auto-created backing store
214
+ and `max_outstanding=` to match the host's pipeline depth.
215
+
216
+ ### Address Maps
217
+
218
+ The `ObiHost` supports address mapping through its `addrmap` attribute, an
219
+ [`AddressMap`](#addressmap) instance. Register names can be used instead of
220
+ numeric addresses in `read()`, `write()`, `read_nowait()`, `write_nowait()`, and
221
+ `poll()`.
222
+
223
+ Configure the map with `addaddrmap()` or by assigning directly to a device index:
224
+
225
+ ```python
226
+ from cocotbext.obi import ObiHost, ObiBus
227
+
228
+ bus = ObiBus.from_prefix(dut, "s_obi")
229
+ host = ObiHost(bus, dut.clk)
230
+
231
+ # Preferred: addaddrmap() updates log column alignment automatically
232
+ host.addaddrmap({
233
+ "STATUS": 0x00,
234
+ "BUSY": 0x04,
235
+ "CONFIG": 0x08,
236
+ "INTERRUPT": 0x0c,
237
+ })
238
+
239
+ # Equivalent for device 0:
240
+ # host.addrmap[0] = { ... }
241
+
242
+ await host.write("STATUS", 0x12)
243
+ await host.read("CONFIG")
244
+ await host.poll("STATUS", 0x1)
245
+
246
+ # Indexed access using string format
247
+ await host.read("STATUS[0]", 0x12)
248
+ await host.read("STATUS[1]", 0x34)
249
+
250
+ # Indexed access using the index parameter (useful with variables)
251
+ for i in range(4):
252
+ await host.write("STATUS", data[i], index=i)
253
+ await host.read("STATUS", expected[i], index=i)
254
+ ```
255
+
256
+ When a map is configured, transaction logs show register names instead of raw
257
+ addresses (for example `Read STATUS : 0x00000012` rather than
258
+ `Read 0x00000000: 0x00000012`). See [tests/test_addrmap](tests/test_addrmap)
259
+ for a complete cocotb example.
260
+
261
+ **Indexed register access:** for register arrays, use either bracket notation
262
+ (`"STATUS[0]"`, `"STATUS[1]"`, …) or the `index` parameter
263
+ (`read("STATUS", data, index=0)`). Both add `index * wbytes` to the base address,
264
+ where `wbytes` is the bus data width in bytes.
265
+
266
+ ### AddressMap
267
+
268
+ `AddressMap` is a protocol-agnostic helper for name-to-address resolution on
269
+ memory-mapped register maps. It is used internally by `ObiHost` (via the
270
+ `addrmap` attribute) and is also exported for standalone use.
271
+
272
+ Import:
273
+
274
+ ```python
275
+ from cocotbext.obi import AddressMap
276
+ ```
277
+
278
+ #### Data model
279
+
280
+ `AddressMap` is a `dict` subclass keyed by **device index**. Each value is a
281
+ plain `dict` mapping **register name** (`str`) to **byte address** (`int`):
282
+
283
+ ```
284
+ AddressMap
285
+ ├── 0 → {"STATUS": 0x00, "CONFIG": 0x08, ...} # device 0
286
+ └── word_bytes, multi_device, _label_width # configuration
287
+ ```
288
+
289
+ Constructor parameters:
290
+
291
+ * _word_bytes_: bus data width in bytes (default `4`). Used for indexed register
292
+ offsets and reverse lookup alignment.
293
+ * _multi_device_: reserve extra column width in log output (default `False`).
294
+ `ObiHost` always constructs the map with `multi_device=False`.
295
+
296
+ #### Forward lookup (name → address)
297
+
298
+ `resolve(addr, device=0, index=-1)` converts a register name or integer address
299
+ to a byte address:
300
+
301
+ * If `addr` is an `int`, it is returned unchanged (plus any `index` offset).
302
+ * If `addr` is a `str`, the base name is looked up in the map for _device_.
303
+ Bracket notation adds `N * word_bytes` for each `[N]` suffix
304
+ (e.g. `"AES_KEY_SHARE0[3]"` → base + 3 × word_bytes).
305
+ * If `index != -1`, `index * word_bytes` is added after name resolution.
306
+
307
+ ```python
308
+ am = AddressMap(word_bytes=4)
309
+ am.add({"STATUS": 0x00, "CONFIG": 0x08})
310
+
311
+ am.resolve(0x08) # 0x08 (integer passthrough)
312
+ am.resolve("STATUS") # 0x00
313
+ am.resolve("STATUS[2]") # 0x08
314
+ am.resolve("STATUS", index=1) # 0x04
315
+ ```
316
+
317
+ #### Reverse lookup (address → name)
318
+
319
+ `format(addr, device=0)` returns the register name for a byte address. When the
320
+ address falls within a mapped register array (aligned to `word_bytes`), bracket
321
+ notation is used for non-zero indices. Unmapped addresses are formatted as
322
+ `0x........`.
323
+
324
+ ```python
325
+ am.format(0x00) # "STATUS"
326
+ am.format(0x08) # "STATUS[2]" (if STATUS base is 0x00, word_bytes=4)
327
+ am.format(0x99) # "0x00000099" (unmapped)
328
+ ```
329
+
330
+ #### Registering maps
331
+
332
+ * `add(addrmap, device=0)`: store a name→address dict for _device_ and recompute
333
+ log column width. This is what `ObiHost.addaddrmap()` delegates to.
334
+ * Direct assignment `am[device] = {...}` also works (dict subclass), but does not
335
+ update column width unless `add()` or `_update_label_width()` is called.
336
+
337
+ #### Log formatting
338
+
339
+ `format_col(label, prefix="")` pads a register label so read/write data columns
340
+ align in log output. `ObiHost` uses this internally when logging transactions.
341
+
342
+ #### Standalone example
343
+
344
+ ```python
345
+ from cocotbext.obi import AddressMap
346
+
347
+ REGS = {
348
+ "STATUS": 0x00,
349
+ "BUSY": 0x04,
350
+ "CONFIG": 0x08,
351
+ }
352
+
353
+ am = AddressMap(word_bytes=4)
354
+ am.add(REGS)
355
+
356
+ addr = am.resolve("CONFIG")
357
+ label = am.format(addr) # "CONFIG"
358
+ col = am.format_col(label) # padded for aligned columns
359
+ ```
360
+
361
+ Unit tests for reverse lookup live in
362
+ [tests/test_format_addr.py](tests/test_format_addr.py). Cocotb integration tests
363
+ are in [tests/test_addrmap](tests/test_addrmap). Polling is covered in
364
+ [tests/test_poll](tests/test_poll).
365
+
366
+ ### Error Types
367
+
368
+ The response-channel `err` bit maps to `OBIError` (with `InvalidAccess`) and the
369
+ `ObiResp` enum, exported for use in custom devices.
370
+
371
+ ## Complete Example
372
+
373
+ ```python
374
+ import cocotb
375
+ from cocotb.triggers import RisingEdge
376
+ from cocotbext.obi import ObiBus, ObiHost
377
+
378
+ @cocotb.test()
379
+ async def test_obi(dut):
380
+ # Create OBI host
381
+ obi_bus = ObiBus.from_prefix(dut, "s_obi")
382
+ obi_host = ObiHost(obi_bus, dut.clk)
383
+
384
+ # Reset
385
+ dut.rst.value = 1
386
+ await RisingEdge(dut.clk)
387
+ await RisingEdge(dut.clk)
388
+ dut.rst.value = 0
389
+ await RisingEdge(dut.clk)
390
+
391
+ # Write some data
392
+ await obi_host.write(0x00, 0x12345678)
393
+ await obi_host.write(0x04, 0xABCDEF00)
394
+
395
+ # Read back and verify
396
+ await obi_host.read(0x00, 0x12345678)
397
+ await obi_host.read(0x04, 0xABCDEF00)
398
+
399
+ # Test 64-bit access on 32-bit bus (auto-splits)
400
+ await obi_host.write(0x100, 0x123456789ABCDEF0)
401
+ await obi_host.read(0x100, 0x123456789ABCDEF0)
402
+ ```
403
+
404
+ ## Pipelined Transactions
405
+
406
+ OBI supports multiple outstanding transactions with **strict in-order completion**. You can enable pipelining by setting `max_outstanding` parameter to 2 or higher:
407
+
408
+ ```python
409
+ from cocotbext.obi import ObiBus, ObiHost, ObiDevice
410
+
411
+ # Create host with pipeline depth of 4
412
+ host = ObiHost(bus, clock, max_outstanding=4)
413
+
414
+ # Create device with same pipeline depth
415
+ device = ObiDevice(bus, clock, max_outstanding=4)
416
+
417
+ # Queue multiple writes - they will be pipelined
418
+ await host.write(0x1000, 0x11111111)
419
+ await host.write(0x1004, 0x22222222)
420
+ await host.write(0x1008, 0x33333333)
421
+ await host.write(0x100C, 0x44444444)
422
+
423
+ # Or queue them without waiting
424
+ host.write_nowait(0x2000, 0xAAAA0000)
425
+ host.write_nowait(0x2004, 0xBBBB1111)
426
+ # ... continue queuing
427
+ await host.wait() # Wait for all to complete
428
+ ```
429
+
430
+ **Key points:**
431
+ - `max_outstanding=1` (default): Strictly sequential behavior, fully backward compatible
432
+ - `max_outstanding > 1`: Enables pipelining for better throughput
433
+ - Host and device should use matching `max_outstanding` values for best performance
434
+ - Responses are **guaranteed** to return in the exact order requests were accepted (OBI requirement)
435
+ - Backpressure is automatic: when the pipeline is full, new requests wait until space is available
436
+
437
+ ### Optional `ObiInterface` (cocotbext-interface)
438
+
439
+ [`cocotbext-interface`](https://github.com/RasmusGOlsen/cocotbext-interface) is **not** required to use this package. `pip install cocotbext-obi` still only needs `cocotb`. Hosts, devices, monitors, and `ObiBus` work as they always have.
440
+
441
+ If you want a SystemVerilog-style `Interface` connection instead of `ObiBus`, install the extra (needs **cocotb 2.x**):
442
+
443
+ $ pip install cocotbext-obi[interface]
444
+
445
+ `ObiInterface` is a drop-in for `ObiBus`: same signal names, `from_prefix` / `from_entity`, and the same `ObiHost` / `ObiMonitor` / `ObiDevice` classes.
446
+
447
+ from cocotbext.obi import ObiInterface, ObiHost, HAVE_COCOTBEXT_INTERFACE
448
+
449
+ bus = ObiInterface.from_prefix(dut, "s_obi")
450
+ obi_driver = ObiHost(bus, dut.clk)
451
+
452
+ `HAVE_COCOTBEXT_INTERFACE` is `True` only when both cocotb 2.x handle types and `cocotbext-interface` imported successfully. Otherwise `import cocotbext.obi` still succeeds, but constructing `ObiInterface` raises `ImportError` with the install command. Tests that need the extra skip when the flag is false (`tests/test_interface`, `tests/test_interface_noid`).
453
+
454
+ ## Testing
455
+
456
+ ### Package Tests
457
+
458
+ The `cocotbext-obi` package includes its own test suite:
459
+
460
+ ```bash
461
+ cd tests/test_slverr
462
+
463
+ # Generate RTL
464
+ make etana
465
+
466
+ # Run with Verilator
467
+ make sim SIM=verilator
468
+
469
+ # Run with Icarus
470
+ make sim SIM=icarus
471
+ ```
472
+
473
+ **Test Results:** ✅ 3/3 PASS (Verilator and Icarus)
474
+
475
+ ### Integration Tests
476
+
477
+ See the PeakRDL-etana `tests/` directory for comprehensive testbenches using cocotbext-obi across 30+ test scenarios.
478
+
479
+ ## License
480
+
481
+ MIT License. See LICENSE file for details.
482
+
483
+ ## References
484
+
485
+ - [OpenHW Group OBI Specification](https://github.com/openhwgroup/obi)
486
+ - [Cocotb Documentation](https://docs.cocotb.org/)
487
+ - [PeakRDL-etana](https://github.com/daxzio/PeakRDL-etana) - Uses this package for OBI testing
488
+