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.
- cocotbext_obi-1.0.0/LICENSE +21 -0
- cocotbext_obi-1.0.0/MANIFEST.in +9 -0
- cocotbext_obi-1.0.0/PKG-INFO +488 -0
- cocotbext_obi-1.0.0/README.md +452 -0
- cocotbext_obi-1.0.0/cocotbext/obi/__init__.py +83 -0
- cocotbext_obi-1.0.0/cocotbext/obi/address_map.py +124 -0
- cocotbext_obi-1.0.0/cocotbext/obi/address_space.py +378 -0
- cocotbext_obi-1.0.0/cocotbext/obi/buddy_allocator.py +92 -0
- cocotbext_obi-1.0.0/cocotbext/obi/bus.py +187 -0
- cocotbext_obi-1.0.0/cocotbext/obi/constants.py +44 -0
- cocotbext_obi-1.0.0/cocotbext/obi/memory.py +101 -0
- cocotbext_obi-1.0.0/cocotbext/obi/obi_base.py +92 -0
- cocotbext_obi-1.0.0/cocotbext/obi/obi_bus.py +71 -0
- cocotbext_obi-1.0.0/cocotbext/obi/obi_device.py +225 -0
- cocotbext_obi-1.0.0/cocotbext/obi/obi_host.py +561 -0
- cocotbext_obi-1.0.0/cocotbext/obi/obi_interface.py +148 -0
- cocotbext_obi-1.0.0/cocotbext/obi/obi_master.py +48 -0
- cocotbext_obi-1.0.0/cocotbext/obi/obi_monitor.py +167 -0
- cocotbext_obi-1.0.0/cocotbext/obi/obi_ram.py +45 -0
- cocotbext_obi-1.0.0/cocotbext/obi/obi_slave.py +45 -0
- cocotbext_obi-1.0.0/cocotbext/obi/sparse_memory.py +113 -0
- cocotbext_obi-1.0.0/cocotbext/obi/utils.py +65 -0
- cocotbext_obi-1.0.0/cocotbext/obi/version.py +1 -0
- cocotbext_obi-1.0.0/cocotbext_obi.egg-info/PKG-INFO +488 -0
- cocotbext_obi-1.0.0/cocotbext_obi.egg-info/SOURCES.txt +31 -0
- cocotbext_obi-1.0.0/cocotbext_obi.egg-info/dependency_links.txt +1 -0
- cocotbext_obi-1.0.0/cocotbext_obi.egg-info/requires.txt +8 -0
- cocotbext_obi-1.0.0/cocotbext_obi.egg-info/top_level.txt +1 -0
- cocotbext_obi-1.0.0/requirements.txt +11 -0
- cocotbext_obi-1.0.0/setup.cfg +77 -0
- cocotbext_obi-1.0.0/setup.py +9 -0
- 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,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
|
+
|