A friendly FPGA that pretends to be your SPI NOR flash.
NORbert uses a Sipeed Tang Primer 25K to emulate a SPI NOR flash chip, backed by 64MB of SDRAM. Load a firmware image over UART, and NORbert will serve it to your target system as if it were a real flash chip -- supporting single, dual, and quad SPI read modes with pipelined SDRAM prefetch for real-time streaming.
- SPI NOR flash emulation with full command support: read, fast read, page program, sector/block/chip erase, JEDEC ID, status registers, SFDP. As on a real part, only status reads are accepted while a program/erase is in progress, and a program/erase is not executed if CS rises mid-byte
- Configurable chip identity at runtime -- load any chip definition from rflasher's RON database to set JEDEC ID, size, and SFDP parameters (defaults to Winbond W25Q64FV)
- Multi-I/O modes: 1-1-1, 1-1-2, 1-2-2, 1-1-4, and 1-4-4 SPI read modes, including continuous read (XIP) mode for 1-2-2/1-4-4 (mode bits
Axh;FFh/FFFFhmode reset) - Fast SPI reads: one-burst lookahead with ping-pong SDRAM buffers supports 40 MHz dummy-less/quad-I/O reads and 50-70 MHz dummy-assisted reads in simulation (see limits below)
- 64MB backing store using two SDRAM chips with byte-serial burst layout for minimal first-byte latency
- Pipelined prefetch: SDRAM reads are issued during SPI address/dummy phases so data is ready on the first clock edge
- 2 Mbaud UART interface for loading and dumping images from a host PC
- FT245 asynchronous FIFO via FT2232H for faster bulk transfers (requires one-time EEPROM configuration)
- SPI bus logging: real-time command/address/byte-count packets drained over either transport via a 512-byte ring FIFO (
monitorsubcommand) - TOCTOU traps: four independent address-match entries that transparently redirect reads to a different SDRAM location on the second access, for exercising verify-then-use flows
- Target flash #HOLD control: drive IO3 low to silence a real flash chip sharing the SPI bus
- Sipeed Tang Primer 25K (Gowin GW5A-LV25MG121)
- Tang Primer 25K Dock ext-board (provides SDRAM and USB-UART)
- SPI signals exposed on PMOD connector J5
- Optional: FT2232H breakout board for FT245 high-speed transport (e.g., CJMCU-FT2232H or any FT2232H module)
NORbert exposes the SPI flash interface on the dock PMOD connector called
PMOD 1 in tangprimer25k.cst (the authoritative pin list). Positions
count from the end of each row away from the GND/3V3 pins:
| SPI Signal | FPGA Pin | PMOD 1 position | Notes |
|---|---|---|---|
/CS# |
G11 |
top 1 | Chip Select (active-low, pulled up) |
SCK |
D11 |
top 2 | SPI Clock (pulled down) |
D0/DI |
B11 |
top 3 | IO0: data in, output in dual/quad reads |
D1/DO |
C11 |
top 4 | IO1: data out |
D2 |
G10 |
bottom 1 | IO2 / /WP (quad reads) |
D3 |
D10 |
bottom 2 | IO3 / /HOLD (quad reads, hold control) |
| Power det. | B10 |
bottom 3 | Optional; ignored by default |
| Debug | C10 |
bottom 4 | Debug output |
GND |
— | 5 (both rows) | Ground |
VCC |
— | 6 (both rows) | 3.3V |
Note: D3 and #HOLD# share the physical IO3 pin. Asserting #HOLD drives it low to silence a real flash on a shared bus. The power-detect input has a pull-down and is bypassed by default (BYPASS_POWER_DETECT in src/top.v); clear that parameter to hold emulation in reset and keep the outputs disabled while the target is unpowered.
Tagged releases publish a Gowin .fs bitstream for the Tang Primer 25K. Download
spi_flash.fs from the latest release
and load it with openFPGALoader:
# Volatile: runs immediately, lost on power-off
openFPGALoader -b tangprimer25k spi_flash.fs
# Persistent: boots automatically after power-on
openFPGALoader -b tangprimer25k -f spi_flash.fsEach release carries one bitstream, named spi_flash.fs so that the
releases/latest/download/... link in the web UI keeps working across
releases, plus a SHA256SUMS file to check it against:
sha256sum --check SHA256SUMSRelease tags exactly match the Rust package version (for example, tag 0.1.0
uses version = "0.1.0" in tool/Cargo.toml). The release workflow rejects a
mismatch.
If you have Nix with flakes enabled, just enter the dev shell:
nix develop # or let direnv handle itThis provides the Gowin IDE (Education Edition), yosys, nextpnr, Apicula, openFPGALoader, verilator, the Rust toolchain with the WebAssembly target, wasm-bindgen-cli, and Trunk for building and serving the Web UI.
Without Nix, you'll need:
- Gowin IDE Education Edition v1.9.11.03 (
gw_shon PATH) - openFPGALoader
- yosys (optional, for linting)
- nextpnr with the Gowin himbaechel target and Apicula (optional, for the open-source build)
- Verilator and a C++ toolchain (for linting/simulation)
- Rust toolchain (for the host tool)
make build # synthesize + place & route
make prog # program FPGA (volatile, lost on power cycle)
make flash # program to flash (persistent)An experimental open-source flow builds the same RTL with Yosys,
nextpnr-himbaechel and Apicula, without the Gowin IDE. The flake pins the
norbert-experimental branches of the Yosys,
nextpnr and
Apicula
forks; stock upstream packages are not equivalent. These are local experimental
patches, not upstream-endorsed support, and the packaged device is GW5A-25A only.
See the toolchain notes for topic branches, tests and
remaining upstreaming work.
nix develop .#oss # optional: shell without proprietary Gowin tools
make build-oss # impl/oss/spi_flash.fs
make prog-oss # program FPGA (volatile)Earlier experimental builds had PLL fuses matching the Gowin build and loaded and verified 8 MiB images over UART on a Tang Primer 25K, across several nextpnr seeds. This is not hardware sign-off of every subsequent toolchain or RTL change. The SPI target interface is untested on hardware, and timing is not signed off: nextpnr's GW5A delays are largely borrowed from GW2A, and it does not analyse the PLL phase offsets, the SDRAM pin timing or the SPI pin timing that the design relies on. Releases stay on the Gowin flow. See timing and CDC contracts for the phase-aware Gowin constraints, required board I/O profile and remaining hardware sign-off. Per-change mapped LUT counts are in RTL utilization.
make lint # Yosys synthesis checks and Verilator lint
make test # SPI, host protocol, SDRAM, TOCTOU, FIFO, logger, UART, FT245
make test-gate # the same benches on the Yosys GW5A netlisttest-gate catches RTL that Yosys maps differently from Gowin synthesis,
such as a tristate it does not recognise. It runs every bench except the
UART and FT245 ones, which peek at state that synthesis renames.
The tests cover SFDP startup/reconfiguration, page-program and AAI semantics,
program latency/refresh, accepted SDRAM addresses during redirected reads, and
three- versus four-byte addressing: every four-byte read command at all start
offsets, the 0xB7/0xE9 address-mode pair, the four-byte program and erase
forms, and a chip that ignores the mode commands entirely.
The transport and buffer blocks are covered by their own benches: the FIFO
against a queue model (tests/fifo_tb.sv), the logger's byte stream against
the format the host decoder expects (tests/logger_tb.sv), the UART over all
256 byte values in both transmitter configurations (tests/uart_tb.sv), and
the FT245 handshake against a time-accurate FT2232H model (tests/ft245_tb.sv).
They use a burst-level memory model or a clock-only PLL stub; they do not replace
Gowin timing analysis or hardware validation of SDRAM pin timing.
make toolThis builds spi-flash-tool at tool/target/release/spi-flash-tool.
The tool also exposes a WebAssembly library for browser frontends.
WebFlashDevice.requestUsb() opens the FT2232H FT245 interface through WebUSB,
while WebFlashDevice.requestSerial() opens the dock's 2 Mbaud UART through Web
Serial. The returned device provides the same protocol operations for either
transport, automatically stops and restores SPI emulation around RAM and
configuration changes, and applies I/O timeouts. Browser
frontends should prefer read_chunks and write_file for bounded-memory
transfers with progress and cancellation callbacks. Check the WASM build with:
rustup target add wasm32-unknown-unknown
cargo check --manifest-path tool/Cargo.toml \
--target wasm32-unknown-unknown --no-default-features --features wasm --libWebFlashDevice.requestUsb() and WebFlashDevice.requestSerial() must be called
from a browser user gesture so the browser can display its device
permission picker. Web Serial currently requires a Chromium-based browser and,
like WebUSB, a secure context (HTTPS or localhost).
A ready-to-use frontend is in web/. From the Nix development shell, build and
serve it with Trunk:
make webui-serveWithout Nix, first install the WebAssembly target, Trunk, and the
wasm-bindgen-cli version recorded in tool/Cargo.lock:
rustup target add wasm32-unknown-unknown
cargo install trunk
cargo install wasm-bindgen-cli --version 0.2.127
make webui-serveOpen http://localhost:8081 and choose either Connect FT245 (WebUSB) or
Connect UART (Web Serial). The UI uses the compiled-in rflasher database to
search for and configure the emulated chip, and provides emulation control,
verified SDRAM uploads and downloads, target-flash #HOLD, decoded live SPI
activity monitoring, and full TOCTOU trap configuration.
The Bitstream tab can program a release or locally built Gowin .fs file
straight into volatile SRAM or persistent configuration flash. This is a Rust/
WebAssembly port of the FT2232H MPSSE/JTAG and Gowin GW5A paths used by
openFPGALoader, built on the existing ftdi-nusb, nusb, and WebUSB support.
Disconnect an active FT245 connection and close native JTAG tools before
programming because they also claim FT2232H interface A. On Linux, unbind the
ftdi_sio driver from interface A if Chromium reports that it cannot claim the
interface. On Windows, interface A must use the WinUSB driver (for example,
configured with Zadig); leave interface B on its normal driver.
The Dock's onboard debugger is a BL616, not an FT2232H. Browser programming therefore needs an external FT2232H connected to the Dock's external-JTAG header. On Dock schematic revision 60033 this connector is J7:
| FT2232H channel A | J7 pin | Signal |
|---|---|---|
| AD3 / CS | 1 | TMS |
| AD1 / DO | 2 | TDI |
| AD0 / SK | 3 | TCK |
| AD2 / DI | 5 | TDO |
| GND | 6 | GND |
| VIO reference | 7 | 3.3 V |
| GND | 8 | BL616_EN |
Leave J7 pin 4 (+5V) unconnected. Grounding BL616_EN disables the onboard
debugger while the external adapter is attached. Pin 7 is a logic-voltage
reference; do not use it to power the Dock or target from the adapter.
Flash programming requires a readable JEDEC SFDP Basic Flash Parameter Table
and checks the write and sector-erase range against the reported capacity
(and the programmer's 24-bit address limit) before changing protection or
issuing an erase. Chips without supported SFDP data are rejected rather than
assuming a capacity. Incomplete .fs configuration data is rejected before
opening the JTAG adapter.
Browser device APIs are unavailable when opening web/index.html directly as
a file:// URL. WebUSB requires Chromium and a secure context (HTTPS or
localhost).
Load a firmware image into NORbert's SDRAM, then let your target SPI master read it back as if it were a real flash chip.
# Check connection
spi-flash-tool version
spi-flash-tool status # running | stopped
# Check whether the SPI fast read path ever fell behind (clears the flags)
spi-flash-tool prefetch
# Load a firmware image (auto stops + starts emulation around the load)
spi-flash-tool load firmware.bin
# Load with verification
spi-flash-tool load firmware.bin --verify
# Dump contents to a file
spi-flash-tool dump output.bin --length 0x100000
# Read a range (hex dump)
spi-flash-tool read 0x0 0x100
# Configure chip identity (uses rflasher chip database)
spi-flash-tool configure W25Q128JV --chips-dir ~/src/rflasher/chips/vendors
# Gate SPI emulation explicitly (the dance above does this automatically)
spi-flash-tool start
spi-flash-tool stop
# List available serial ports
spi-flash-tool portsThe configure command loads a chip definition from rflasher's RON database and sends the JEDEC ID, size, and a generated SFDP table to the FPGA. The chip name is matched by substring, so W25Q128 is enough if it's unambiguous. Without configuration, NORbert defaults to Winbond W25Q64FV.
At power-on the FPGA boots in the STOPPED state -- the SPI pins are held in reset and the host can always reach the tool, regardless of what the target board is doing. load automatically stops emulation, writes the image, and starts it again. Use start/stop/status for manual control.
Use -p /dev/ttyUSBx if your device isn't on the default /dev/ttyUSB0.
monitor streams decoded SPI activity from NORbert in real time. It works over either UART or FT245 and is safe to run while the target is actively reading:
spi-flash-tool monitorExample output while flashprog reads a 4 KB region:
TXN# COMMAND ADDRESS INFO
------------------------------------------------------------
1 0x9F READ_JEDEC_ID
2 0x05 READ_STATUS
3 0x05 READ_STATUS
4 0xBB DUAL_IO_READ 0x001000
end: 4097 bytes from 0x001000
The monitor also tracks double-reads of the same (opcode, address) pair and flags them as TOCTOU candidates. Press Ctrl+C to stop. The underlying protocol is a poll-based ring-buffer drain (CMD_LOGPOLL = 0x3A); packet types are 0xA1 (command), 0xA2 (address), 0xA3 (end + byte count), 0xA4 (TOCTOU trap fired), and 0xA6 (dropped event count), with 0xA0 as the per-poll terminator. Log bytes equal to 0xA0 or 0xA5 are sent as 0xA5 0x00 and 0xA5 0x05.
The byte ring holds 503 bytes, backed by six queued capture frames and one
frame being emitted. Each frame holds all events seen on one system edge;
frames are served in capture order, with CMD, ADDR, TRAP, END order for ties.
When the frame queue fills, new events are dropped without overwriting old
payloads. A 0xA6 packet reports the gap with a two-byte big-endian count
(saturated at 65535) before later accepted events. The monitor displays
LOG GAP and discards the previous transaction association. A transaction
can still lose some packets and keep others, but surviving packets stay
whole and chronological. tests/logger_tb.sv checks cross-type ordering
behind a full ring and accounts for every retained or dropped event.
Four independent trap entries redirect matching reads to a different SDRAM location on the second (and subsequent) access. The first matching read is let through unchanged -- it arms the trap. The first SDRAM burst (up to 8 bytes, depending on starting alignment) always comes from the original address; subsequent bursts come from the replacement. All matching entries become triggered, and the highest-index already-triggered match selects the replacement when traps overlap. Only array reads count as accesses and are matched, by their start address: SFDP reads (a separate address space) and the addresses of program and erase commands neither trigger nor redirect.
# Configure: any read in 0x001000-0x001FFF gets redirected to 0x101000-0x101FFF
spi-flash-tool toctou set 1 0x001000 0xFFF000 0x101000
spi-flash-tool toctou arm 1
# First target read of 0x001000 returns the original data.
# Second target read of 0x001000 returns the replacement data.
# Clear the triggered flag so the next read is "first" again:
spi-flash-tool toctou reset 1
# Tear everything down:
spi-flash-tool toctou reset-allArguments to toctou set are <index 0..3> <start-address> <match-mask> <replace-base>, all as byte addresses. 1-bits in the mask must match exactly; 0-bits are don't-care. The replacement preserves the "don't-care" bits of the original address.
When NORbert shares a SPI bus with a real flash chip, hold on drives IO3 low continuously, asserting #HOLD on the target flash so it tristates and ignores all commands. hold off releases it. Mutually exclusive with quad I/O because IO3 is shared.
For much faster bulk transfers (~5 MB/s vs ~200 KB/s over UART), connect an FT2232H module and use --ft245:
# List connected FT2232H devices
spi-flash-tool ft-list
# Use FT245 instead of UART for any command
spi-flash-tool --ft245 version
spi-flash-tool --ft245 load firmware.bin --verify
spi-flash-tool --ft245 dump output.bin --length 0x100000
spi-flash-tool --ft245 configure W25Q128JV --chips-dir ~/src/rflasher/chips/vendors
# Select a specific FT2232H by serial number (when multiple are connected)
spi-flash-tool --ft245 --ft-serial FT6XXXXX load firmware.binThe FT2232H is used in asynchronous 245 FIFO mode. This requires a one-time EEPROM configuration to set Channel A to "245 FIFO" mode using FT_PROG (Windows) or ftdi_eeprom (Linux). No special BitMode is set at runtime -- the host tool just opens the device and reads/writes normally.
Wiring: Connect the FT2232H Channel A pins to the FPGA dock as follows:
| FT2232H Pin | Signal | FPGA Pin | Dock Location (tangprimer25k.cst) |
|---|---|---|---|
| AD0-AD3 | D[0:3] | H5, H8, G7, F5 | PMOD 3 top 4, 3, 2, 1 |
| AD4-AD7 | D[4:7] | H7, G8, G5, J5 | PMOD 3 bottom 3, 2, 1, 4 |
| RXF# | ft_rxf_n | A11 | PMOD 2 top 1 |
| TXE# | ft_txe_n | E11 | PMOD 2 top 2 |
| RD# | ft_rd_n | K11 | PMOD 2 top 3 |
| WR# | ft_wr_n | L5 | PMOD 2 top 4 |
CLKOUT and OE# are not used in async mode. All signals are 3.3V LVCMOS.
Sustained SPI clock limits are set by the SDRAM prefetch pipeline
(spi_prefetch.v and sdram.v). Each 8-byte SDRAM burst takes ~12 system clock cycles (120 MHz)
from post to data valid. Instead of posting just-in-time, the SPI engine keeps
one burst in flight at all times (one-burst lookahead with ping-pong buffers),
so the limit is throughput (one burst per ~12 sysclks), not single-burst
latency. SDRAM runs at CAS latency 2 (in spec to 133 MHz for the W9825G6KH-6)
with a byte-serial burst layout, so the first beat already completes bytes 0-1.
Validated by simulation (make test, tests/quad_fast_tb.sv: all start
offsets 0-7, row/bank crossings, and a refresh that becomes due at every
system-clock offset into the transaction, with an SDRAM bank-protocol
checker on the command bus). During an array read, refresh only starts
right after a lookahead burst has been dispatched, so a refresh pair can
no longer land in front of a burst the SPI side is about to need. 60-70 MHz operation
also needs timing closure past the default 30 MHz spi_clk constraint plus
signal-integrity validation on the PMOD leads -- simulated margins below do
not replace that.
| Command | Mode | Simulated max (all offsets) | Notes |
|---|---|---|---|
| 0x03 Read | 1-1-1 | 40 MHz | No dummy clocks; hardest first burst |
| 0x0B Fast Read | 1-1-1 | 70 MHz | 8 dummy clocks |
| 0x3B Dual Output | 1-1-2 | 50 MHz (spot) | 8 dummy clocks |
| 0xBB Dual I/O | 1-2-2 | 60 MHz | 4 mode clocks |
| 0x6B Quad Output | 1-1-4 | 70 MHz | 8 dummy clocks |
| 0xEB Quad I/O | 1-4-4 | 40 MHz | 6 mode clocks; offset 7 is limiting |
The matrix requires both correct data and ready beats at the output edges. Earlier 50 MHz claims for 0x03/0xEB relied on an eventual burst-valid flag and a linear pattern whose high bits could hide stale first bits. The new mixed-address pattern and per-beat checks expose those failures, including short reads. The same limits apply to the corresponding four-byte opcodes.
Known corners detected by the simulated prefetch fault checks:
- Quad-I/O offset 7 at 50 MHz and above can need the second burst before it is ready. Use <= 40 MHz for all offsets, or validate a narrower range.
- Slow reads above 40 MHz can run out of their 3.5-clock first-burst window, particularly at high offsets. Real NOR flashes likewise impose a lower clock limit on 0x03 than on dummy-assisted reads.
- 70 MHz dual-I/O reads can underrun; 60 MHz is the all-offset limit.
If you need more: the levers are open-row (skip re-ACTIVATE on same-row bursts, saves ~2 sysclks per burst) and master-configured extra dummy clocks for 0xEB/0xBB, not a faster SDRAM clock.
The corners above are detected in the FPGA, not just in simulation. Run a target read, then ask the FPGA what happened:
spi-flash-tool prefetchThe flags are latched in the FPGA and cleared by the read that reports them, so the check covers everything since the previous one:
- underrun (
0x01) -- an output bit used a byte whose SDRAM beat was not ready when the output path sampled it. The data is untrusted even if stale bits happened to equal the intended data. Checked on each data sampling edge, not only at burst end, so short reads are covered. - thin margin (
0x02) -- the upcoming burst's first beat was not ready at the burst boundary. Not necessarily corruption; individual beat validity determines whether later output actually underruns.
The command is safe to run while the target is reading, and the web UI has the same check as Check prefetch on the Device panel. An underrun means the frequency, start offset or SDRAM latency of that read is outside the supported envelope in the table above -- the data the target received cannot be trusted.
src/
top.v Top-level module, clock/reset, bus wiring, TOCTOU address mux
spi_trx.v SPI flash transceiver (command decoder + data path)
spi_prefetch.v SDRAM burst requests, ping-pong buffer selection and the prefetch fault flags
sdram.v Dual-chip SDRAM controller, byte-serial bursts, ping-pong prefetch
spi_flash_cmds.vh Emulated SPI flash opcodes and read wait states
host_protocol.vh Host serial protocol opcodes and log packet types
glue.v Composition of independent host and SPI program clients
host_protocol.v UART/FT245 protocol, configuration, TOCTOU, LOGPOLL, fault latch and LEDs
spi_program.v Page-buffer BSRAM, NOR program/erase/RMW and completion
sdram_client_mux.v Stable client ownership and acceptance routing
logger.v Ordered capture-frame queue, loss accounting and 512-byte output FIFO
uart.v UART TX/RX (2 Mbaud)
ft245.v FT2232H async 245 FIFO interface
fifo.v Synchronous FIFO (first-word-fall-through)
util.v Clock divider, PWM, synchronizers
pll.v PLL: 50MHz -> 120MHz with phase-shifted outputs
tool/
src/main.rs Host-side CLI (Rust) for loading/dumping/monitoring/TOCTOU over UART or FT245
src/chip.rs Chip definition loading from rflasher RON database
src/sfdp.rs SFDP/BFPT table generation from chip definitions
src/host_protocol.vh defines these values for the FPGA; cargo test checks
that tool/src/protocol.rs matches it.
All opcodes reply with a single 0x01 ACK unless otherwise noted. The FPGA
accepts command bytes from whichever port (UART or FT245) first delivers one
while the parser is idle, and routes the response back to the same port.
Each port has a receive FIFO; while a command is in progress, bytes from the
other port wait there until it completes. A parser left mid-command returns
to idle after ~546 us without a byte, or ~35 ms inside a RAMWRITE payload, so
a USB stall within a data block is not mistaken for an abandoned command.
VERSION reports the protocol version, currently 7. The host tool talks to
any version from 3 up to its own and enables commands by version, but it
refuses a newer bitstream rather than guess at its protocol: a version 7
bitstream needs a tool from the same release or later.
| Opcode | Name | Args | Reply |
|---|---|---|---|
0x30 |
VERSION | none | 1 byte (current: 0x07) |
0x31 |
RAMREAD | 3-byte burst addr + 2-byte burst count | count*8 data bytes |
0x32 |
RAMWRITE | 3-byte burst addr + 2-byte burst count + data | 0x01 |
0x33 |
CHIPCONFIG | JEDEC(3) + flags + erase_bursts(3) + sfdp_len + sfdp | 0x01 |
0x34 |
START | none -- enable SPI emulation | 0x01 |
0x35 |
STOP | none -- hold spi_trx in reset | 0x01 |
0x36 |
STATUS | none | 0x01 running / 0x02 stopped |
0x37 |
HOLDCTL | 1 byte: 0x01 assert, 0x00 release |
0x01 |
0x38 |
LOGCTL | 1 byte: 0x01 start capture, 0x00 stop capture |
0x01 |
0x39 |
TOCTOU | sub-command + args (see below) | 0x01 |
0x3A |
LOGPOLL | none | log bytes terminated by 0xA0 |
0x3B |
PREFETCH | none | 1 byte: 0x80 valid + 0x01 underrun + 0x02 thin |
A clean PREFETCH reply is 0x80 (not 0x00, which the host discards as
transport noise); 0x81, 0x82 and 0x83 report faults. PREFETCH is a
single byte with no argument, like STATUS. It is safe to issue in any
emulation state, and reading it clears the flags it reports, so a fault is
reported to exactly one reader.
TOCTOU sub-commands (all prefixed with opcode 0x39):
| Sub | Name | Args |
|---|---|---|
0x01 |
SET | index + start(3) + mask(3) + replace(3), all big-endian |
0x02 |
ARM | index |
0x03 |
DISARM | index |
0x04 |
RESET | index -- clear triggered flag |
0x05 |
RESET_ALL | none -- disarm + clear all four |
RAMREAD/RAMWRITE/CHIPCONFIG are only accepted while emulation is stopped, to avoid racing the SPI fast path on SDRAM, and their bytes wait (on either port) while the target holds CS low or an SPI program/erase owns SDRAM. All other commands, including HOLDCTL, LOGCTL and TOCTOU with their arguments, touch neither and are processed immediately, so the host can reach the tool even while a target is hammering the bus.
- Arisotura/spi_flash -- SPI flash emulation logic adapted from this project
- ArthurHeymans/tang_20k_spi_flash -- my first attempt at porting Arisotura's project, targeting different Tang hardware with 8MB of embedded DRAM
- Trammel Hudson's SPISpy -- the original FPGA-based SPI flash emulator that inspired this project