cocotbext-ospi 0.2.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_ospi-0.2.0/LICENSE +21 -0
- cocotbext_ospi-0.2.0/PKG-INFO +424 -0
- cocotbext_ospi-0.2.0/README.md +398 -0
- cocotbext_ospi-0.2.0/cocotbext/ospi/__init__.py +41 -0
- cocotbext_ospi-0.2.0/cocotbext/ospi/devices/__init__.py +38 -0
- cocotbext_ospi-0.2.0/cocotbext/ospi/devices/mt35xu512aba.py +94 -0
- cocotbext_ospi-0.2.0/cocotbext/ospi/devices/mx25um51345g.py +112 -0
- cocotbext_ospi-0.2.0/cocotbext/ospi/devices/profile.py +94 -0
- cocotbext_ospi-0.2.0/cocotbext/ospi/ospi_bus.py +33 -0
- cocotbext_ospi-0.2.0/cocotbext/ospi/ospi_config.py +37 -0
- cocotbext_ospi-0.2.0/cocotbext/ospi/ospi_flash.py +195 -0
- cocotbext_ospi-0.2.0/cocotbext/ospi/ospi_master.py +187 -0
- cocotbext_ospi-0.2.0/cocotbext/ospi/sfdp.py +401 -0
- cocotbext_ospi-0.2.0/cocotbext/ospi/verilog/controller/xspi_controller.v +307 -0
- cocotbext_ospi-0.2.0/cocotbext/ospi/verilog/controller/xspi_controller_test.v +84 -0
- cocotbext_ospi-0.2.0/cocotbext/ospi/verilog/devices/generate_sfdp.py +96 -0
- cocotbext_ospi-0.2.0/cocotbext/ospi/verilog/devices/mt35xu512aba.v +465 -0
- cocotbext_ospi-0.2.0/cocotbext/ospi/verilog/devices/mt35xu512aba_sfdp.vh +132 -0
- cocotbext_ospi-0.2.0/cocotbext/ospi/verilog/devices/mt35xu512aba_test.v +38 -0
- cocotbext_ospi-0.2.0/cocotbext/ospi/verilog/devices/mx25um51345g.v +619 -0
- cocotbext_ospi-0.2.0/cocotbext/ospi/verilog/devices/mx25um51345g_sfdp.vh +132 -0
- cocotbext_ospi-0.2.0/cocotbext/ospi/verilog/devices/mx25um51345g_test.v +33 -0
- cocotbext_ospi-0.2.0/cocotbext/ospi/verilog/ospi_flash.v +265 -0
- cocotbext_ospi-0.2.0/cocotbext/ospi/verilog/ospi_flash_test.v +37 -0
- cocotbext_ospi-0.2.0/cocotbext/ospi/xspi_flash.py +359 -0
- cocotbext_ospi-0.2.0/cocotbext_ospi.egg-info/PKG-INFO +424 -0
- cocotbext_ospi-0.2.0/cocotbext_ospi.egg-info/SOURCES.txt +35 -0
- cocotbext_ospi-0.2.0/cocotbext_ospi.egg-info/dependency_links.txt +1 -0
- cocotbext_ospi-0.2.0/cocotbext_ospi.egg-info/requires.txt +1 -0
- cocotbext_ospi-0.2.0/cocotbext_ospi.egg-info/top_level.txt +1 -0
- cocotbext_ospi-0.2.0/pyproject.toml +51 -0
- cocotbext_ospi-0.2.0/setup.cfg +4 -0
- cocotbext_ospi-0.2.0/tests/test_controller.py +236 -0
- cocotbext_ospi-0.2.0/tests/test_interop.py +134 -0
- cocotbext_ospi-0.2.0/tests/test_mt35xu512aba.py +400 -0
- cocotbext_ospi-0.2.0/tests/test_mx25um51345g.py +771 -0
- cocotbext_ospi-0.2.0/tests/test_ospi_flash.py +157 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jithesh Vijay
|
|
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,424 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cocotbext-ospi
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: OSPI flash verification for cocotb, with a JEDEC flash model
|
|
5
|
+
Author-email: Jithesh Vijay <jitheshvijay67@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/JitheshVijay/cocotbext-ospi
|
|
8
|
+
Project-URL: Issues, https://github.com/JitheshVijay/cocotbext-ospi/issues
|
|
9
|
+
Keywords: cocotb,ospi,qspi,spi,flash,verification,hdl,rtl
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Framework :: cocotb
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Software Development :: Testing
|
|
21
|
+
Requires-Python: >=3.9
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: cocotb>=2.0
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
|
|
27
|
+
# cocotbext-ospi
|
|
28
|
+
|
|
29
|
+
OSPI flash verification for [cocotb](https://www.cocotb.org/): a bus driver,
|
|
30
|
+
a device-level API over the JEDEC command set, and a NOR flash model to test
|
|
31
|
+
against — single, dual, quad and **octal** I/O.
|
|
32
|
+
|
|
33
|
+
Requires **cocotb 2.0+**. Tests run on Icarus Verilog.
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
pip install cocotbext-ospi
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Why another SPI extension
|
|
40
|
+
|
|
41
|
+
[`cocotbext-spi`](https://github.com/schang412/cocotbext-spi) covers
|
|
42
|
+
single-lane SPI. This one covers flash specifically, up to an eight-lane
|
|
43
|
+
data bus, with the write-enable latch, status polling, page program and
|
|
44
|
+
sector erase — and it targets cocotb 2.x.
|
|
45
|
+
|
|
46
|
+
## Pointing a testbench at the models
|
|
47
|
+
|
|
48
|
+
The Verilog ships inside the package, so there is nothing to vendor. Ask the
|
|
49
|
+
package where it is:
|
|
50
|
+
|
|
51
|
+
```make
|
|
52
|
+
VERILOG_DIR := $(shell python3 -c \
|
|
53
|
+
"import cocotbext.ospi as o; print(o.verilog_dir())")
|
|
54
|
+
|
|
55
|
+
VERILOG_SOURCES = $(VERILOG_DIR)/devices/mx25um51345g.v
|
|
56
|
+
VERILOG_SOURCES += $(VERILOG_DIR)/devices/mx25um51345g_test.v
|
|
57
|
+
COMPILE_ARGS += -I$(VERILOG_DIR)/devices
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Or from Python, `cocotbext.ospi.verilog_dir()` returns a `pathlib.Path`.
|
|
61
|
+
|
|
62
|
+
## Usage
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
import cocotb
|
|
66
|
+
from cocotb.clock import Clock
|
|
67
|
+
from cocotbext.ospi import OspiFlash, CMD_OIOR
|
|
68
|
+
|
|
69
|
+
@cocotb.test()
|
|
70
|
+
async def test_flash(dut):
|
|
71
|
+
cocotb.start_soon(Clock(dut.clk, 20, unit="ns").start())
|
|
72
|
+
|
|
73
|
+
flash = OspiFlash(dut)
|
|
74
|
+
await flash.initialize()
|
|
75
|
+
|
|
76
|
+
assert await flash.read_id() == [0xC2, 0x80, 0x39]
|
|
77
|
+
|
|
78
|
+
# program() sets WEL, then polls the status register until WIP clears.
|
|
79
|
+
await flash.program(0x1000, [0xDE, 0xAD, 0xBE, 0xEF])
|
|
80
|
+
|
|
81
|
+
assert await flash.read(0x1000, 4) == [0xDE, 0xAD, 0xBE, 0xEF]
|
|
82
|
+
assert await flash.read(0x1000, 4, opcode=CMD_OIOR) == [0xDE, 0xAD, 0xBE, 0xEF]
|
|
83
|
+
|
|
84
|
+
await flash.erase_sector(0x1000)
|
|
85
|
+
assert await flash.read(0x1000, 4) == [0xFF] * 4
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## The protocol, and two things that catch people out
|
|
89
|
+
|
|
90
|
+
SPI mode 0: the master launches data while the clock is low, the device
|
|
91
|
+
samples it on the rising edge, and vice versa.
|
|
92
|
+
|
|
93
|
+
**How wide the opcode is depends on the protocol, and this trips people up.**
|
|
94
|
+
The names say it: in **1-4-4** — a quad read issued to a part still in
|
|
95
|
+
ordinary SPI — the leading `1` means the opcode goes out on one lane and only
|
|
96
|
+
the address and data widen. In **8-8-8** the part has been switched into
|
|
97
|
+
octal wholesale, so the opcode is eight lanes too, and it comes as a pair
|
|
98
|
+
with its extension byte.
|
|
99
|
+
|
|
100
|
+
Mixing these up is the most common reason a controller talks to nothing, and
|
|
101
|
+
it is why `xspi_controller` takes a separate lane count for the command
|
|
102
|
+
phase rather than assuming either. (The sibling `cocotbext-qspi` is the
|
|
103
|
+
1-4-4 case throughout, so there the opcode really is always single-lane.)
|
|
104
|
+
|
|
105
|
+
**Programming only clears bits.** NOR flash needs an erase to set a bit back
|
|
106
|
+
to 1. Programming `0x0F` over `0xF0` gives `0x00`, not `0x0F`.
|
|
107
|
+
|
|
108
|
+
### What it looks like on the wire
|
|
109
|
+
|
|
110
|
+
All three diagrams below are generated from a real simulation — `capture.py`
|
|
111
|
+
runs the transactions, Icarus dumps a VCD, and `render.py` draws it. Nothing
|
|
112
|
+
is drawn by hand, so they cannot drift away from what the model does.
|
|
113
|
+
|
|
114
|
+
An octal I/O read. The opcode goes out one bit per clock on a single lane;
|
|
115
|
+
only then does the bus widen to all eight for the address and data — a whole
|
|
116
|
+
byte per clock. Note the dummy cycles, where neither side drives while the
|
|
117
|
+
bus turns around:
|
|
118
|
+
|
|
119
|
+

|
|
120
|
+
|
|
121
|
+
The same byte read at every width. This is what the wide modes buy you —
|
|
122
|
+
40 clocks single-lane down to 21 octal, for one byte at the same address:
|
|
123
|
+
|
|
124
|
+

|
|
125
|
+
|
|
126
|
+
A status read while a program is in flight. The device answers `0x01` — WIP
|
|
127
|
+
set — which is what `wait_ready()` polls for:
|
|
128
|
+
|
|
129
|
+

|
|
130
|
+
|
|
131
|
+
To regenerate them:
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
make -C docs/waveforms # run the sim, dump capture.vcd
|
|
135
|
+
make -C docs/waveforms svg # capture.vcd -> *.svg
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Commands
|
|
139
|
+
|
|
140
|
+
| Opcode | Name | Address | Data |
|
|
141
|
+
|---|---|---|---|
|
|
142
|
+
| `0x06` | Write enable | — | — |
|
|
143
|
+
| `0x04` | Write disable | — | — |
|
|
144
|
+
| `0x05` | Read status | — | 1 lane, repeats |
|
|
145
|
+
| `0x9F` | JEDEC id | — | 1 lane, 3 bytes |
|
|
146
|
+
| `0x03` | Read | 1 lane | 1 lane |
|
|
147
|
+
| `0xBB` | Fast read dual I/O | 2 lanes | 2 lanes, after mode byte + dummy |
|
|
148
|
+
| `0xEB` | Fast read quad I/O | 4 lanes | 4 lanes, after mode byte + dummy |
|
|
149
|
+
| `0x8B` | Fast read octal I/O | 8 lanes | 8 lanes, after mode byte + dummy |
|
|
150
|
+
| `0x02` | Page program | 1 lane | 1 lane; needs WEL, sets WIP |
|
|
151
|
+
| `0x20` | Sector erase (4 KB) | 1 lane | — ; needs WEL, sets WIP |
|
|
152
|
+
|
|
153
|
+
Status register: bit 0 `WIP` (write in progress), bit 1 `WEL` (write enable
|
|
154
|
+
latch). `wait_ready()` polls it rather than assuming a fixed delay, which is
|
|
155
|
+
what a real controller must do.
|
|
156
|
+
|
|
157
|
+
`HOLD_N` is **active low**: high is normal operation, and pulling it low
|
|
158
|
+
freezes the interface mid-transaction without losing state.
|
|
159
|
+
|
|
160
|
+
## Real device models
|
|
161
|
+
|
|
162
|
+
Alongside the generic model there are models of specific parts, built from
|
|
163
|
+
their datasheets and cross-checked against Linux's `drivers/mtd/spi-nor`:
|
|
164
|
+
|
|
165
|
+
| Part | Protocols | Command extension | Octal entry |
|
|
166
|
+
|---|---|---|---|
|
|
167
|
+
| **Macronix MX25UM51345G** | 1S-1S-1S, 8S-8S-8S, 8D-8D-8D | **inverted** (`~opcode`) | CR2 `0x00000000` |
|
|
168
|
+
| **Micron MT35XU512ABA** | 1S-1S-1S, 8D-8D-8D | **repeated** (`opcode`) | CFR1V then CFR0V |
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
from cocotbext.ospi.devices import MX25UM51345G
|
|
172
|
+
from cocotbext.ospi.xspi_flash import XspiFlash
|
|
173
|
+
|
|
174
|
+
flash = XspiFlash(dut, MX25UM51345G)
|
|
175
|
+
await flash.initialize() # the part boots single-lane
|
|
176
|
+
assert await flash.read_id() == [0xC2, 0x81, 0x3A]
|
|
177
|
+
|
|
178
|
+
await flash.enter_octal() # writes CR2, switches protocol
|
|
179
|
+
assert await flash.read_id() == [0xC2, 0x81, 0x3A] # now over eight lanes
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Three things real parts do that a generic octal model does not
|
|
183
|
+
|
|
184
|
+
**Octal commands come in pairs.** In 8-8-8 the opcode goes out eight lanes
|
|
185
|
+
wide, immediately followed by an extension byte. Macronix sends the
|
|
186
|
+
bitwise complement (`8READ` is `EC`/`13`), Micron repeats the opcode. Linux
|
|
187
|
+
calls these `SPI_NOR_EXT_INVERT` and `SPI_NOR_EXT_REPEAT`. Send the wrong
|
|
188
|
+
one and the model rejects the command — the two disagree about this
|
|
189
|
+
deliberately, and each has a test proving it refuses the other's form.
|
|
190
|
+
|
|
191
|
+
That rejection is a modelling choice, flagged as such in both models. The
|
|
192
|
+
datasheets and Linux say what a controller must *send*; neither says what
|
|
193
|
+
silicon does with a mismatched extension. Refusing it is what turns a
|
|
194
|
+
controller configured for the wrong vendor into an obvious failure instead
|
|
195
|
+
of undefined behaviour.
|
|
196
|
+
|
|
197
|
+
**Commands change shape with the protocol.** `RDSR` takes no address and no
|
|
198
|
+
dummy cycles in SPI, but on the Macronix part in OPI it grows a 4-byte
|
|
199
|
+
address and four dummy cycles. `RDID` likewise. Addresses are 4 bytes, not
|
|
200
|
+
3.
|
|
201
|
+
|
|
202
|
+
**Dummy cycles are configurable and you must track them.** `DC[2:0]` in
|
|
203
|
+
Macronix CR2 `0x300` selects 20/18/16/14/12/10/8/6 cycles depending on clock
|
|
204
|
+
frequency; Micron's CFR1V holds the count directly. A controller that does
|
|
205
|
+
not follow the register reads garbage — there is a test that walks the whole
|
|
206
|
+
table.
|
|
207
|
+
|
|
208
|
+
### 8D-8D-8D
|
|
209
|
+
|
|
210
|
+
Both parts run at double transfer rate: a bit per lane on **both** clock
|
|
211
|
+
edges, so eight lanes move two bytes per clock. That is why an odd number of
|
|
212
|
+
bytes cannot be transferred in that mode, and why leaving it means writing
|
|
213
|
+
CFR0V and CFR1V together in one 2-byte write — which is exactly what Linux
|
|
214
|
+
does.
|
|
215
|
+
|
|
216
|
+
Macronix has separate STR and DTR octal reads (`8READ` = `EC`/`13`,
|
|
217
|
+
`8DTRD` = `EE`/`11`) selected by CR2 bit 0 or bit 1; `enter_octal()` takes
|
|
218
|
+
the protocol you want. Its DTR mode also enforces datasheet note 5: **the
|
|
219
|
+
start address must be even**. An odd one is rejected rather than quietly
|
|
220
|
+
returning the neighbouring byte.
|
|
221
|
+
|
|
222
|
+
The DTR edge handling is validated against PicoSoC's `spiflash.v` quad-DTR
|
|
223
|
+
read (`0xED`), an independently written model, for the same reason the rest
|
|
224
|
+
of the interop suite exists.
|
|
225
|
+
|
|
226
|
+
### DQS
|
|
227
|
+
|
|
228
|
+
A separate pin, not part of `SIO[7:0]`. The device strobes it alongside read
|
|
229
|
+
data so a controller can capture with the data rather than with its own
|
|
230
|
+
clock, which is what makes high-speed DTR reads timing-closable.
|
|
231
|
+
|
|
232
|
+
The shape is taken from the Rev 1.3 timing figures, read from the artwork —
|
|
233
|
+
the text extraction carries only bare `DQS` row labels:
|
|
234
|
+
|
|
235
|
+
| Phase | DQS |
|
|
236
|
+
|---|---|
|
|
237
|
+
| Command, extension, address | **held high** |
|
|
238
|
+
| Dummy | low |
|
|
239
|
+
| Data | toggles with the clock |
|
|
240
|
+
|
|
241
|
+
It is **not** parked low while busy, which matters to a controller that
|
|
242
|
+
gates on it: *DQS low* means dummy-or-idle, not idle alone. Getting this
|
|
243
|
+
backwards is exactly the sort of thing a model tested only against its own
|
|
244
|
+
driver never notices — the tests would assert whatever the model did.
|
|
245
|
+
|
|
246
|
+
DTR always strobes. STR only does so if `DOS` (CR2 `0x200` bit 1) asks, and
|
|
247
|
+
a controller that enables DQS capture without setting it waits for edges
|
|
248
|
+
that never come.
|
|
249
|
+
|
|
250
|
+
Two things the datasheet does not settle, flagged in the model and pinned by
|
|
251
|
+
tests so the choices are visible:
|
|
252
|
+
|
|
253
|
+
- Only **one** STR-OPI figure carries a DQS row at all — the array read.
|
|
254
|
+
Every STR-OPI register read is drawn without one, so whether `DOS` makes
|
|
255
|
+
`RDSR` or `RDID` strobe is undocumented. The model says yes.
|
|
256
|
+
- No figure shows DQS after the final data byte, so returning low at the end
|
|
257
|
+
of a burst is an assumption.
|
|
258
|
+
|
|
259
|
+
### SFDP
|
|
260
|
+
|
|
261
|
+
Both models carry a real SFDP image, so a driver can discover a part instead
|
|
262
|
+
of being told about it:
|
|
263
|
+
|
|
264
|
+
```python
|
|
265
|
+
info = await flash.discover()
|
|
266
|
+
info.size_bytes # 67108864 (512 Mb)
|
|
267
|
+
info.address_bytes_name # '4 only'
|
|
268
|
+
info.dtr # True
|
|
269
|
+
info.page_size # 256
|
|
270
|
+
info.erase_types # [(4096, 0x21), (65536, 0xDC)]
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
`RDSFDP` (`0x5A`) changes shape with the protocol — 3 address bytes and 8
|
|
274
|
+
dummy cycles in SPI, 4 and 20 in OPI — so discovery has to know which mode
|
|
275
|
+
it is in. Both profiles carry both shapes, and a test reads the same table
|
|
276
|
+
each way.
|
|
277
|
+
|
|
278
|
+
Both parts also carry an **xSPI Profile 1.0 table** (JESD251, id `0xFF05`),
|
|
279
|
+
which is how a part advertises its octal DTR capability rather than being
|
|
280
|
+
told: the fast-read opcode, the dummy cycles needed at each frequency, and
|
|
281
|
+
the shape `RDSR` takes in octal.
|
|
282
|
+
|
|
283
|
+
```python
|
|
284
|
+
info = await flash.configure_from_sfdp(mhz=200)
|
|
285
|
+
# reads Profile 1.0 and points the driver's octal read at the advertised
|
|
286
|
+
# opcode and dummy count -- no profile constants involved
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
That the two parts disagree here is the point: Macronix `RDSR` takes a
|
|
290
|
+
4-byte address and 4 dummy cycles in octal, Micron's takes none and 8. A
|
|
291
|
+
controller hardcoded for one misreads the other, which is what Profile 1.0
|
|
292
|
+
exists to prevent. Each part has a test asserting its own shape and the
|
|
293
|
+
other's.
|
|
294
|
+
|
|
295
|
+
The tests also check the table is not lying: every advertised dummy count is
|
|
296
|
+
programmed into CR2 and the read has to still work.
|
|
297
|
+
|
|
298
|
+
The tables are built by `cocotbext/ospi/sfdp.py` and emitted into the models
|
|
299
|
+
by `cocotbext/ospi/verilog/devices/generate_sfdp.py`. Defining them once and generating the
|
|
300
|
+
Verilog is what stops the model and the parser drifting apart — and the
|
|
301
|
+
tests read back through the parser exactly what the generator put in.
|
|
302
|
+
|
|
303
|
+
```
|
|
304
|
+
python3 cocotbext/ospi/verilog/devices/generate_sfdp.py # regenerate the ROMs
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Reset
|
|
308
|
+
|
|
309
|
+
`initialize()` issues the `RSTEN`/`RST` pair. A part left in octal by a
|
|
310
|
+
previous run cannot understand a single-lane command, so the sequence is
|
|
311
|
+
sent in every protocol the profile supports; the one the part is actually in
|
|
312
|
+
takes effect and the rest are ignored as malformed. Without this, tests
|
|
313
|
+
quietly depend on whatever mode the previous one left behind.
|
|
314
|
+
|
|
315
|
+
### What is not modelled
|
|
316
|
+
|
|
317
|
+
Micron's suspend/resume and its lock registers; Macronix's SPB and lock
|
|
318
|
+
register (the volatile DPB layer is modelled, the non-volatile one is not);
|
|
319
|
+
ECC and CRC; the secured OTP array; and SFDP tables beyond BFPT, Profile 1.0
|
|
320
|
+
and 4BAIT. Timing parameters are simulation-convenient rather than
|
|
321
|
+
datasheet-accurate — `PROGRAM_NS` and `ERASE_NS` are parameters, not the
|
|
322
|
+
real tPP/tSE.
|
|
323
|
+
|
|
324
|
+
The arrays are a small window rather than the full 64 MB so simulations stay
|
|
325
|
+
fast; capacity is reported honestly in both the JEDEC ID and SFDP.
|
|
326
|
+
|
|
327
|
+
```
|
|
328
|
+
make -C tests -f Makefile.mx25 # Macronix, 38 tests
|
|
329
|
+
make -C tests -f Makefile.mt35 # Micron, 24 tests
|
|
330
|
+
make -C tests -f Makefile.controller # controller DUT, 8 tests
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
## A controller as DUT
|
|
334
|
+
|
|
335
|
+
Everything above points a driver at a flash model. `cocotbext/ospi/verilog/controller/`
|
|
336
|
+
inverts that: an `xspi_controller` is the RTL under test, driving the
|
|
337
|
+
MX25UM51345G model, with cocotb poking only its command interface. It never
|
|
338
|
+
touches the flash pins — if the controller gets a phase wrong, the bytes come
|
|
339
|
+
back wrong and nothing in Python can paper over it.
|
|
340
|
+
|
|
341
|
+
```
|
|
342
|
+
make -C tests -f Makefile.controller # 8 tests
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
One command per handshake: opcode, optional extension byte, address, address
|
|
346
|
+
width, dummy cycles, lane counts, direction and length. Nothing in it is
|
|
347
|
+
specific to a particular flash, so the same RTL drives the part in
|
|
348
|
+
single-lane SPI and in octal.
|
|
349
|
+
|
|
350
|
+
It is deliberately small — a sequencer, not a product. What it is for is
|
|
351
|
+
being something real to point the models at, and it earned that immediately:
|
|
352
|
+
writing it exposed an opcode-width error in this README, because the driver
|
|
353
|
+
and the model both happened to be right while the prose was wrong. A closed
|
|
354
|
+
loop of our own components could not have surfaced that.
|
|
355
|
+
|
|
356
|
+
Single transfer rate only. DTR needs data on both edges and two sample
|
|
357
|
+
points per period, and the counters step once per `sclk` period, so it is a
|
|
358
|
+
real change rather than a parameter.
|
|
359
|
+
|
|
360
|
+
## Bus signals
|
|
361
|
+
|
|
362
|
+
`OspiBus.from_entity(dut)` picks up `clk`, `csb`, `io` and `HOLD_N`, plus
|
|
363
|
+
`io_out` and `io_oe`.
|
|
364
|
+
|
|
365
|
+
A simulator will not let a testbench drive an `inout` net, so the top level
|
|
366
|
+
splits the master's half into a value and a **per-lane** output enable:
|
|
367
|
+
|
|
368
|
+
```verilog
|
|
369
|
+
wire [7:0] io;
|
|
370
|
+
genvar g;
|
|
371
|
+
generate
|
|
372
|
+
for (g = 0; g < 8; g = g + 1) begin : lane
|
|
373
|
+
assign io[g] = io_oe[g] ? io_out[g] : 1'bz;
|
|
374
|
+
end
|
|
375
|
+
endgenerate
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
Per-lane, not bus-wide: in single-lane mode the master drives `io0` while
|
|
379
|
+
the device answers on `io1`.
|
|
380
|
+
|
|
381
|
+
Note also that `csb` is left uninitialised in `ospi_flash_test.v`. The model
|
|
382
|
+
frames transactions on chip-select edges, and an initialiser there races
|
|
383
|
+
cocotb's first write at time 0 — the edge is lost and the device never
|
|
384
|
+
starts. `initialize()` drives the sequence explicitly.
|
|
385
|
+
|
|
386
|
+
## Testing
|
|
387
|
+
|
|
388
|
+
Two suites, and the split matters:
|
|
389
|
+
|
|
390
|
+
```
|
|
391
|
+
make -C tests # against our own JEDEC model: 12 tests
|
|
392
|
+
make -C tests -f Makefile.interop # against PicoSoC's spiflash.v: 5 tests
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
The interop suite drives
|
|
396
|
+
[`spiflash.v`](https://github.com/YosysHQ/picorv32) — a model this project
|
|
397
|
+
did not write — and checks the bytes against known `$readmemh` content.
|
|
398
|
+
|
|
399
|
+
That distinction earned its keep. Testing only against our own model proves
|
|
400
|
+
the driver and the model agree; it does not prove either is right. Driving
|
|
401
|
+
somebody else's model immediately found that the master was dropping the
|
|
402
|
+
first bit of every byte — our model had the same off-by-one assumption, so
|
|
403
|
+
the closed loop had been happily agreeing with itself.
|
|
404
|
+
|
|
405
|
+
**Scope of that check:** `spiflash.v` is a four-lane part, so interop covers
|
|
406
|
+
the single, dual and quad paths. There is no comparable open-source octal
|
|
407
|
+
model, so the eight-lane path is exercised only against our own model. Treat
|
|
408
|
+
octal as less hardened than the rest.
|
|
409
|
+
|
|
410
|
+
## Layout
|
|
411
|
+
|
|
412
|
+
| Path | Contents |
|
|
413
|
+
|---|---|
|
|
414
|
+
| `cocotbext/ospi/ospi_flash.py` | `OspiFlash` — JEDEC command set, status polling, hold |
|
|
415
|
+
| `cocotbext/ospi/ospi_master.py` | `OspiMaster` — byte transfers at 1/2/4/8 lanes |
|
|
416
|
+
| `cocotbext/ospi/ospi_bus.py` | `OspiBus` — signal bundle |
|
|
417
|
+
| `cocotbext/ospi/ospi_config.py` | `OspiConfig`, `lanes_for_mode` |
|
|
418
|
+
| `cocotbext/ospi/verilog/ospi_flash.v` | NOR flash model: WEL, WIP, page program, sector erase, hold |
|
|
419
|
+
| `cocotbext/ospi/verilog/ospi_flash_test.v` | cocotb top level |
|
|
420
|
+
| `tests/reference/` | third-party model for interop (ISC, see its README) |
|
|
421
|
+
|
|
422
|
+
## Licence
|
|
423
|
+
|
|
424
|
+
MIT. `tests/reference/spiflash.v` is ISC, © Claire Xenia Wolf.
|