feat: config-flash passthrough bridge via STARTUPE2, real board-exclusive flash access (EXP-0077)
Implements the user's board architecture: config flash wired exclusively to the FPGA, host (ESP32) reaches it only through the FPGA. flash_spi_master.v is a plain byte-wide SPI master using STARTUPE2 to reclaim CCLK after configuration (the real, Xilinx- documented "indirect SPI flash programming" technique, UG470 p94-96). New opcode 0x40 FLASH_XFER in spi_host_bridge_v3.v relays bytes byte-for-byte between host and the physical flash bus -- the host decides the exact SPI NOR command sequence (verified against the real W25Q32JV datasheet), this RTL knows nothing about flash semantics. Found and fixed two real bugs during verification: a byte-assembly off-by-one in flash_spi_master.v, and a genuine protocol-latency bug in the FLASH_XFER opcode's response timing (needed 2 trailing margin bytes, not 1 -- the internal flash transfer doesn't start until the triggering byte finishes, so 1 byte of margin isn't enough). 39/39 tests pass end to end (host SPI -> bridge -> flash_spi_master -> behavioral flash model). Wired into n2_system_ddr3_top.v with real pin constraints (flash_mosi =K17/flash_miso=K18/flash_cs_n=L13, the same pins reserved-but-unused in EXP-0075) and BITSTREAM.CONFIG.PERSIST=FALSE made explicit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MUG92aM9m68TRc4rG55BcC
This commit is contained in:
@@ -4770,3 +4770,101 @@ next_action: none blocking -- remaining work is scaling past N=2,
|
||||
building a real activation-fetch engine, and finalizing PCB-specific
|
||||
constraints (SPI/reset pin LOCs) once the board layout itself is
|
||||
underway. All disclosed, none of it changes today's real signoff.
|
||||
|
||||
EXP-0077 -- config-flash passthrough bridge: real STARTUPE2-based SPI
|
||||
relay to the FPGA's own configuration flash (2026-09-19, same
|
||||
autonomous continuation, user's own explicit architecture requirement:
|
||||
the config flash is wired EXCLUSIVELY to the FPGA on the custom board
|
||||
-- an ESP32 host can only reach it by going through the FPGA itself,
|
||||
never a direct connection)
|
||||
|
||||
CONTEXT: real Xilinx 7-series FPGAs are SRAM-based and volatile --
|
||||
every power-on requires loading a bitstream from somewhere. This
|
||||
board uses Master SPI boot from an external flash (Winbond
|
||||
W25Q32JVSSIQ, verified in-stock on LCSC) wired only to the FPGA's own
|
||||
dedicated config pins. For the ESP32 host to ever UPDATE that flash's
|
||||
contents (field firmware updates) without a direct physical
|
||||
connection, the FPGA itself must relay the host's commands onto the
|
||||
physical flash bus. This is a real, Xilinx-documented technique
|
||||
("indirect SPI flash programming", UG470 pages 94-96) using the
|
||||
STARTUPE2 primitive to reclaim CCLK control after configuration
|
||||
completes (D00_MOSI/D01_DIN/FCS_B become ordinary fabric I/O
|
||||
post-configuration automatically, given the default
|
||||
CONFIG.PERSIST=FALSE bitstream setting).
|
||||
|
||||
Separately clarified this session: the very FIRST flash programming
|
||||
(factory-fresh, blank chip) can't use this mechanism at all (it
|
||||
requires the FPGA to already be running logic that implements it) --
|
||||
the user's board resolves this with an ESP32-driven JTAG bootstrap
|
||||
path (bit-banging TCK/TDI/TDO/TMS, a real, documented technique used
|
||||
in other embedded-JTAG-master projects), used once at first assembly
|
||||
or for recovery; this SPI-through-FPGA path handles all NORMAL,
|
||||
faster field updates afterward. Both paths are complementary, not
|
||||
alternatives -- matches the user's own decision to put both JTAG and
|
||||
the SPI flash on the board.
|
||||
|
||||
METHOD: (1) hardware/v3/rtl/flash_spi_master.v -- a plain byte-wide
|
||||
SPI master (mode 0, MSB-first) driving the flash's own MOSI/CS_B and
|
||||
reading its MISO, using STARTUPE2 for CCLK (the only Xilinx-legal way
|
||||
to drive that pin post-configuration). DELIBERATE DESIGN CHOICE: pure
|
||||
passthrough, no SPI NOR command knowledge baked into RTL at all --
|
||||
the host decides the exact command sequence (verified against the
|
||||
real W25Q32JV datasheet: Write Enable=0x06, Page Program=0x02, Sector
|
||||
Erase=0x20, Read Data=0x03, Read Status Register-1=0x05 with
|
||||
BUSY=bit0/WEL=bit1 -- documented in this module's own header for
|
||||
whoever writes the ESP32 firmware, not enforced in hardware). (2) new
|
||||
opcode 0x40 FLASH_XFER in spi_host_bridge_v3.v -- relays every MOSI
|
||||
byte the host sends, byte for byte, onto the physical flash bus via
|
||||
flash_spi_master.v, and relays the flash's own response back on MISO.
|
||||
|
||||
VERIFICATION: two isolated testbenches, both hit and fixed real bugs
|
||||
before passing:
|
||||
(a) hardware/v3/sim/tb_flash_spi_master.v -- flash_spi_master.v
|
||||
alone against a real-command-set behavioral W25Q32JV model.
|
||||
Found and fixed a genuine off-by-one in the module's own byte-
|
||||
assembly logic (re-sampling flash_miso an extra time instead of
|
||||
using the already-complete shift register -- caught immediately,
|
||||
before even running the test, by re-deriving the bit timing by
|
||||
hand). ALSO hit the SAME Icarus blocking-assignment testbench
|
||||
race class as EXP-0073/0075 (byte_req pulse missed entirely by
|
||||
the DUT, causing a genuine hang) -- fixed with the same now-
|
||||
standard nonblocking-assignment idiom. Result: 4/4 PASS.
|
||||
(b) hardware/v3/sim/tb_spi_host_bridge_v3.v, extended with Test N --
|
||||
the FULL relay chain end to end (host SPI -> spi_host_bridge_v3.v
|
||||
-> flash_spi_master.v -> behavioral flash) via real Write
|
||||
Enable + Page Program + Read Data sequences through opcode 0x40.
|
||||
Found a REAL protocol-latency bug (not a testbench artifact):
|
||||
the FLASH_XFER opcode's own documented "response ready by the
|
||||
next host byte" latency convention was WRONG by one byte --
|
||||
flash_spi_master.v's own transfer (~640ns at this project's real
|
||||
155.039MHz ui_clk) doesn't even START until the triggering host
|
||||
byte finishes, so it lands PARTWAY through the very next host
|
||||
byte's own transmission, corrupting that byte's early bits (a
|
||||
real, reproduced single-bit corruption, root-caused via a full
|
||||
signal trace, not guessed). Fixed by requiring TWO trailing
|
||||
margin bytes, not one -- a full extra host byte period is always
|
||||
comfortably longer than one internal flash transfer at any
|
||||
realistic host SPI clock rate, unlike a single byte of margin
|
||||
which isn't. Corrected in both the module's own header and the
|
||||
test. Result: 39/39 PASS (all prior tests unaffected).
|
||||
|
||||
Wired into hardware/v3/rtl/n2_system_ddr3_top.v (flash_spi_master.v
|
||||
instantiated, spi_host_bridge_v3.v's 5 new flash_* ports connected)
|
||||
and hardware/v3/constraints/n2_system_ddr3_top.xdc (real pins: flash_
|
||||
mosi=K17/flash_miso=K18/flash_cs_n=L13 -- the SAME physical pins
|
||||
reserved-but-unused in EXP-0075's own constraints, now legitimately
|
||||
claimed for this purpose; BITSTREAM.CONFIG.PERSIST explicitly set
|
||||
FALSE, self-documenting the real dependency this module has on it).
|
||||
|
||||
DECISION: the config-flash passthrough path is functionally correct
|
||||
and real-pin-constrained. STARTUPE2 itself (a real Xilinx primitive,
|
||||
only usable once per design, verified here only via a simulation-only
|
||||
stub -- see flash_spi_master.v's own header) still needs a REAL
|
||||
in-context P&R run to confirm it places/routes correctly and that
|
||||
CONFIG.PERSIST/STARTUPE2 genuinely coexist without conflicting with
|
||||
the MIG's own use of the configuration infrastructure -- this is
|
||||
real, not yet done, disclosed as the immediate next_action.
|
||||
|
||||
next_action: real in-context P&R re-verification (synth+impl) with
|
||||
flash_spi_master.v + the new XDC pin/PERSIST constraints included --
|
||||
first real placement check for STARTUPE2 in this project.
|
||||
|
||||
Reference in New Issue
Block a user