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.
Files changed (37) hide show
  1. cocotbext_ospi-0.2.0/LICENSE +21 -0
  2. cocotbext_ospi-0.2.0/PKG-INFO +424 -0
  3. cocotbext_ospi-0.2.0/README.md +398 -0
  4. cocotbext_ospi-0.2.0/cocotbext/ospi/__init__.py +41 -0
  5. cocotbext_ospi-0.2.0/cocotbext/ospi/devices/__init__.py +38 -0
  6. cocotbext_ospi-0.2.0/cocotbext/ospi/devices/mt35xu512aba.py +94 -0
  7. cocotbext_ospi-0.2.0/cocotbext/ospi/devices/mx25um51345g.py +112 -0
  8. cocotbext_ospi-0.2.0/cocotbext/ospi/devices/profile.py +94 -0
  9. cocotbext_ospi-0.2.0/cocotbext/ospi/ospi_bus.py +33 -0
  10. cocotbext_ospi-0.2.0/cocotbext/ospi/ospi_config.py +37 -0
  11. cocotbext_ospi-0.2.0/cocotbext/ospi/ospi_flash.py +195 -0
  12. cocotbext_ospi-0.2.0/cocotbext/ospi/ospi_master.py +187 -0
  13. cocotbext_ospi-0.2.0/cocotbext/ospi/sfdp.py +401 -0
  14. cocotbext_ospi-0.2.0/cocotbext/ospi/verilog/controller/xspi_controller.v +307 -0
  15. cocotbext_ospi-0.2.0/cocotbext/ospi/verilog/controller/xspi_controller_test.v +84 -0
  16. cocotbext_ospi-0.2.0/cocotbext/ospi/verilog/devices/generate_sfdp.py +96 -0
  17. cocotbext_ospi-0.2.0/cocotbext/ospi/verilog/devices/mt35xu512aba.v +465 -0
  18. cocotbext_ospi-0.2.0/cocotbext/ospi/verilog/devices/mt35xu512aba_sfdp.vh +132 -0
  19. cocotbext_ospi-0.2.0/cocotbext/ospi/verilog/devices/mt35xu512aba_test.v +38 -0
  20. cocotbext_ospi-0.2.0/cocotbext/ospi/verilog/devices/mx25um51345g.v +619 -0
  21. cocotbext_ospi-0.2.0/cocotbext/ospi/verilog/devices/mx25um51345g_sfdp.vh +132 -0
  22. cocotbext_ospi-0.2.0/cocotbext/ospi/verilog/devices/mx25um51345g_test.v +33 -0
  23. cocotbext_ospi-0.2.0/cocotbext/ospi/verilog/ospi_flash.v +265 -0
  24. cocotbext_ospi-0.2.0/cocotbext/ospi/verilog/ospi_flash_test.v +37 -0
  25. cocotbext_ospi-0.2.0/cocotbext/ospi/xspi_flash.py +359 -0
  26. cocotbext_ospi-0.2.0/cocotbext_ospi.egg-info/PKG-INFO +424 -0
  27. cocotbext_ospi-0.2.0/cocotbext_ospi.egg-info/SOURCES.txt +35 -0
  28. cocotbext_ospi-0.2.0/cocotbext_ospi.egg-info/dependency_links.txt +1 -0
  29. cocotbext_ospi-0.2.0/cocotbext_ospi.egg-info/requires.txt +1 -0
  30. cocotbext_ospi-0.2.0/cocotbext_ospi.egg-info/top_level.txt +1 -0
  31. cocotbext_ospi-0.2.0/pyproject.toml +51 -0
  32. cocotbext_ospi-0.2.0/setup.cfg +4 -0
  33. cocotbext_ospi-0.2.0/tests/test_controller.py +236 -0
  34. cocotbext_ospi-0.2.0/tests/test_interop.py +134 -0
  35. cocotbext_ospi-0.2.0/tests/test_mt35xu512aba.py +400 -0
  36. cocotbext_ospi-0.2.0/tests/test_mx25um51345g.py +771 -0
  37. 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
+ ![Fast read octal I/O](https://raw.githubusercontent.com/JitheshVijay/cocotbext-ospi/v0.2.0/docs/waveforms/octal-read.png)
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
+ ![One byte at every width](https://raw.githubusercontent.com/JitheshVijay/cocotbext-ospi/v0.2.0/docs/waveforms/width-comparison.png)
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
+ ![Read status](https://raw.githubusercontent.com/JitheshVijay/cocotbext-ospi/v0.2.0/docs/waveforms/read-status.png)
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.