Compare commits
2
Commits
0303aacb9f
...
v3-artix7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
785e9461a0 | ||
|
|
e3fe007469 |
@@ -1,16 +1,14 @@
|
||||
# FPGA-Neural V3 — Architecture Analysis: Timing, Bottlenecks, and Recommended Interventions
|
||||
|
||||
Scope: the current, real, P&R-verified V3 design (`hardware/v3/`, branch
|
||||
`v3-artix7` for the physically-fabricated board; branch
|
||||
`n16-timing-closure` for the real, consolidated follow-up below),
|
||||
updated through EXP-0097/0098 (a real MAC-pipeline fix that closes
|
||||
timing for the WHOLE real family — N=2/N=8/N=16 — not just N=8). The
|
||||
board **currently in physical fabrication** uses N=8's own original,
|
||||
unmodified EXP-0095/0096 signoff (`n8_system_ddr3_top.v`, 32-bit DDR3,
|
||||
155.039MHz, WNS=0.000ns exact, 8 real parallel PEs). Separately, on an
|
||||
isolated real branch, EXP-0097/0098 found that one shared-core fix
|
||||
closes N=16 too (WNS=+0.269ns) and improves N=2 (+0.389ns) and N=8
|
||||
(+0.108ns) as well — see §5.6 for the full real story. Every
|
||||
`v3-artix7`, now fast-forward merged with the former
|
||||
`n16-timing-closure` branch). **UPDATE (2026-09-22): the real physical
|
||||
fabrication target moved to N=16** (`n16_system_ddr3_top.v`, with the
|
||||
real EXP-0097 MAC-pipeline fix, WNS=+0.269ns, 16 real parallel PEs) —
|
||||
no physical board change needed, DDR3/SPI/flash/config pins are
|
||||
identical regardless of `N_GROUPS`. The same MAC-pipeline fix also
|
||||
improves N=2 (+0.389ns) and N=8 (+0.108ns), both kept as real, valid
|
||||
fallback signoffs — see §5.6 for the full real story. Every
|
||||
number in this document is either
|
||||
directly measured (real simulation trace, real P&R report) or a calculation
|
||||
built from directly-measured building blocks — the two are labeled
|
||||
|
||||
@@ -0,0 +1,386 @@
|
||||
# FPGA-Neural — ESP32 Host Firmware Specification
|
||||
|
||||
Real, from-source specification for the ESP32-S3 host firmware, derived
|
||||
directly from the actual RTL protocol implementation (`spi_host_bridge_v3.v`,
|
||||
`neural_director_grouped.v`, `result_writeback.v`, `host_mem_bridge.v`,
|
||||
`flash_spi_master.v`) and the real, closed timing signoff in
|
||||
`docs/PHYSICAL_REALIZATION.md`. Nothing below is guessed — every field
|
||||
layout, address formula, and timing number is read directly from the
|
||||
committed RTL's own header comments or real, measured project data.
|
||||
|
||||
**Status**: this is a specification only. No ESP32-side firmware exists
|
||||
yet in this repository — this document exists to make that firmware
|
||||
buildable without re-deriving the protocol from RTL by hand. Real,
|
||||
disclosed scope: management SPI protocol, job submission, result
|
||||
readback, raw memory access, config-flash relay, boot/bootstrap. Out
|
||||
of scope: FreeRTOS task structure, Wi-Fi/network layer, higher-level
|
||||
model-graph scheduling above "submit one job" — those are real
|
||||
firmware design decisions, not yet made anywhere in this project.
|
||||
|
||||
**Real fabrication target (2026-09-22)**: N=16 (`n16_system_ddr3_top.v`,
|
||||
16 real parallel processing elements, 4 groups × 4 PEs). This document
|
||||
is written for N=16 throughout; N=2/N=8 fallback configurations use
|
||||
the byte-for-byte identical protocol (§3), differing only in the real
|
||||
octet/pairing requirement of §4.2.
|
||||
|
||||
---
|
||||
|
||||
## 1. Physical interfaces
|
||||
|
||||
Two electrically separate SPI buses, plus JTAG. Full real pin
|
||||
assignments: `docs/PINOUT.md` / `docs/PHYSICAL_REALIZATION.md` §2.
|
||||
|
||||
| Bus | FPGA role | Purpose | Pins (bank 15/16, LVCMOS33) |
|
||||
|---|---|---|---|
|
||||
| Management SPI | **slave** | Job submission, status, raw memory access, config-flash relay | sclk=A15, mosi=B16, miso=B17, cs_n=A16 |
|
||||
| Config-flash SPI | **master** (relayed) | Bitstream/config flash access, reached only via `FLASH_XFER` (§6) | not directly host-visible — internal to the FPGA |
|
||||
| JTAG | target | Factory/recovery programming | TCK=E10, TDI=E11, TMS=E12, TDO=E13 |
|
||||
|
||||
Additional real, dedicated signals on the management SPI bus's own
|
||||
bank:
|
||||
|
||||
| Signal | Pin | Direction (ESP32 side) | Purpose |
|
||||
|---|---|---|---|
|
||||
| `sys_rst` | G13 (tentative real pin, §7 of PHYSICAL_REALIZATION.md) | output | Board-level reset, separate from the SPI-driven soft-reset (§3) |
|
||||
| `data_ready_n` | D14 | input, active-low | Sticky IRQ — see §5.1 |
|
||||
|
||||
**Real, important note**: the ESP32 has **no direct electrical
|
||||
connection** to the config flash. The only path is `FLASH_XFER` (§6),
|
||||
relayed through the FPGA over the management SPI bus.
|
||||
|
||||
---
|
||||
|
||||
## 2. Boot / bootstrap sequence
|
||||
|
||||
Three real, distinct paths (`docs/PHYSICAL_REALIZATION.md` §5):
|
||||
|
||||
1. **Factory-first / recovery (JTAG, ESP32-driven, one-time or rare)**
|
||||
— a factory-fresh board has a **blank** config flash. The FPGA
|
||||
cannot relay `FLASH_XFER` commands until it is already running
|
||||
logic that does that — a real chicken-and-egg constraint. The only
|
||||
way to bootstrap a blank board is for the ESP32 to bit-bang JTAG
|
||||
(TAP state machine, IR/DR shifting) and either (a) load a bitstream
|
||||
directly into the FPGA's configuration memory (volatile — lost on
|
||||
power-cycle), or (b) run Vivado's own real "indirect SPI flash
|
||||
programming" sequence over JTAG to write the flash for the first
|
||||
time. **Real, disclosed gap: this JTAG bit-banging firmware does
|
||||
not exist yet anywhere in this project** — it is real, necessary,
|
||||
unbuilt software work, out of scope for this document beyond
|
||||
flagging it.
|
||||
2. **Normal boot (Master SPI, autonomous, every power-on)** — the FPGA
|
||||
self-configures from the flash via its own dedicated configuration
|
||||
hardware (mode pins `M[2:0]=001`, §2.4 of PHYSICAL_REALIZATION.md).
|
||||
**No ESP32 involvement at all.** Firmware's own real startup
|
||||
sequence should simply wait for `init_calib_complete` (readable via
|
||||
`REG_READ` on STATUS, §3.3, bit3) before issuing any job or memory
|
||||
access — DDR3 traffic before real PHY calibration completes is
|
||||
unsafe.
|
||||
3. **Field firmware update (`FLASH_XFER`, §6)** — once the FPGA is
|
||||
already running (path 2 completed), the ESP32 can rewrite the
|
||||
config flash by relaying raw SPI-NOR bytes through the FPGA. This
|
||||
is the **only** path from ESP32 to flash during normal operation.
|
||||
|
||||
### 2.1 Real, recommended firmware startup sequence
|
||||
|
||||
```
|
||||
1. Assert sys_rst (G13), hold >= a few clk periods, release.
|
||||
(Real note: sys_rst's own real, permanent board location is still
|
||||
tentative per PHYSICAL_REALIZATION.md §7 -- confirm against the
|
||||
real board schematic before relying on this pin number.)
|
||||
2. Wait for the FPGA's own real Master-SPI autonomous boot to complete
|
||||
-- there is no host-visible "boot done" signal separate from
|
||||
init_calib_complete; DONE (pin P10) is a real, physical
|
||||
configuration-status pin if the ESP32 has a GPIO wired to it
|
||||
(board-dependent, not guaranteed).
|
||||
3. REG_READ(0x00 DEVICE_ID) -- confirm 32'h4E50_5601 ("NPV" + protocol
|
||||
version 1) before trusting anything else. A mismatch means either
|
||||
the flash holds the wrong bitstream, or config-boot has not
|
||||
actually completed -- do not proceed.
|
||||
4. Poll REG_READ(0x02 STATUS) bit3 (init_calib_complete) until set.
|
||||
Real, measured DDR3 calibration timing has not been characterized
|
||||
on real hardware yet (only in simulation, where SIM_BYPASS_INIT_CAL
|
||||
shortcuts it) -- budget real time for this, do not assume it is
|
||||
instantaneous.
|
||||
5. Only once bit3 is set: safe to issue WRITE_MEM (weight/activation
|
||||
preload, SS3) and WRITE_JOB (SS5).
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Management SPI protocol — real opcode reference
|
||||
|
||||
One opcode byte (MSB-first), sent as the first byte of a CS-low SPI
|
||||
transaction; multi-byte fields are MSB-first. Authoritative source:
|
||||
`spi_host_bridge_v3.v`'s own header comment — this table is a summary.
|
||||
|
||||
| Opcode | Name | Direction | Payload (MOSI) | Response (MISO) |
|
||||
|---|---|---|---|---|
|
||||
| `0x00` | NOP | — | 0 bytes | inert (0x00) |
|
||||
| `0x0F` | RESET | host→FPGA | 0 bytes | pulses `soft_rst_pulse` for 1 real clk cycle after CS rises |
|
||||
| `0x10` | WRITE_JOB | host→FPGA | 16 bytes (§4.1) | — |
|
||||
| `0x20` | STATUS | host→FPGA | 0 bytes | 1 byte (§3.1) |
|
||||
| `0x01` | WRITE_MEM | host→FPGA | 4 + 2·N bytes (§6.1) | — |
|
||||
| `0x02` | READ_MEM | host→FPGA | 6 bytes | 2·N bytes (§6.1) |
|
||||
| `0x30` | REG_WRITE | host→FPGA | 5 bytes (§3.2) | — |
|
||||
| `0x31` | REG_READ | host→FPGA | 1 byte | 4 bytes (§3.3) |
|
||||
| `0x40` | FLASH_XFER | bidir | N bytes | N+2 bytes (§6.2) |
|
||||
|
||||
**Real, load-bearing rule**: any opcode byte not in this table is
|
||||
treated as NOP — the bus is never wedged by an unrecognized command.
|
||||
Do not rely on this for real protocol negotiation; it exists purely as
|
||||
a safety fallback.
|
||||
|
||||
### 3.1 `0x20` STATUS — 1 response byte
|
||||
|
||||
| Bit | Meaning |
|
||||
|---|---|
|
||||
| 0 | `job_busy` — a `WRITE_JOB` is currently waiting on `job_in_ready` |
|
||||
| 1 | `mem_busy` — a `WRITE_MEM`/`READ_MEM` is waiting on `mem_ready` |
|
||||
| 2 | `last_job_accepted` (sticky, cleared by the next `WRITE_JOB`) |
|
||||
| 7:3 | reserved, always 0 |
|
||||
|
||||
### 3.2 `0x30` REG_WRITE / `0x31` REG_READ
|
||||
|
||||
`REG_WRITE`: `byte0=reg_addr[7:0]`, `byte1:4=value[31:0]` MSB-first —
|
||||
applied the instant the last byte lands (purely internal, no backend
|
||||
handshake).
|
||||
|
||||
`REG_READ`: `byte0=reg_addr[7:0]`, then 4 response bytes MSB-first. An
|
||||
unmapped address reads back `0xFFFFFFFF` (deliberately distinct from a
|
||||
real `0`, so firmware can tell "unmapped register" from "real zero").
|
||||
Writing a read-only or unmapped register is inert (accepted, no
|
||||
effect).
|
||||
|
||||
### 3.3 Real register map (v1)
|
||||
|
||||
| Addr | Name | R/W | Real meaning |
|
||||
|---|---|---|---|
|
||||
| `0x00` | DEVICE_ID | RO | `32'h4E50_5601` ("NPV" + protocol version 1) |
|
||||
| `0x01` | CONTROL | RW | bit0: write 1 to pulse `soft_rst_pulse` (same physical effect as `0x0F`); always reads back 0; bits[31:1] reserved |
|
||||
| `0x02` | STATUS | RO | bit0 `job_busy`, bit1 `mem_busy`, bit2 `last_job_accepted`, bit3 `init_calib_complete`, bit4 `dir_error`; bits[31:5] reserved |
|
||||
| `0x03` | N_SLOTS | RO | real, total parallel-PE count this bitstream was built with (**16** for the current N=16 fabrication target) — read this instead of hardcoding the core count in firmware |
|
||||
|
||||
---
|
||||
|
||||
## 4. Job submission
|
||||
|
||||
### 4.1 `0x10` WRITE_JOB — real 16-byte payload
|
||||
|
||||
| Bytes | Field | Real convention |
|
||||
|---|---|---|
|
||||
| 0:1 | `node_id[15:0]` | host-assigned identifier, returned unchanged with the result (§5) |
|
||||
| 2:5 | `x_base[25:0]` | byte-address convention (26-bit `JOB_ADDR_WIDTH`); byte2's MSBs are `{6'b0, x_base[25:24]}` |
|
||||
| 6:9 | `w_base[25:0]` | same convention — **must match, together with `n_tiles`, across the real octet requirement of §4.2** |
|
||||
| 10:11 | `n_tiles[15:0]` | number of `P_IN=8`-wide tiles to stream (real activation length = `n_tiles × 8` bytes) |
|
||||
| 12:15 | `result_addr[25:0]` | where `result_writeback.v` will write the real result — see §5 for the real readback formula |
|
||||
|
||||
`job_in_valid` is asserted and **held** until `job_in_ready` also
|
||||
reads 1 in the same real cycle — never a blind, unacknowledged pulse.
|
||||
Firmware does not need to do anything special for this; it is entirely
|
||||
internal to the bridge.
|
||||
|
||||
### 4.2 REAL, CRITICAL N=16 requirement: octet dispatch
|
||||
|
||||
**This is the single most important real constraint in this
|
||||
document.** `neural_director_grouped.v` (the real N=16 job scheduler)
|
||||
only dispatches jobs in **groups of 8** — it inspects the **8 oldest**
|
||||
entries in its own internal queue and requires **all eight** to share
|
||||
the exact same `w_base` **and** `n_tiles`. If they do, they are
|
||||
dispatched together (one octet = one real systolic group's own job,
|
||||
weight fetched once, broadcast to 4 PEs × 2 lanes). If they do not,
|
||||
**the queue stalls — permanently, with no in-band recovery.**
|
||||
Real, verified behavior (`tb_neural_director_grouped.v`): a mismatched
|
||||
octet does not corrupt anything and does not silently mis-pair jobs —
|
||||
it simply stops draining the queue. The **only** real recovery is a
|
||||
hardware reset (`sys_rst`, or `CONTROL` register bit0 / `RESET`
|
||||
opcode) — there is no software-visible "skip this job" or "flush the
|
||||
queue" command.
|
||||
|
||||
**Real, mandatory firmware discipline**: submit exactly 8 `WRITE_JOB`
|
||||
transactions sharing the same `w_base` and `n_tiles`, back-to-back,
|
||||
with no other job submitted in between, for every real layer-position
|
||||
batch. If a real workload's position count is not a multiple of 8,
|
||||
**pad the batch** (e.g. repeat the last real position, or submit a
|
||||
real dummy position with a `result_addr` the firmware will simply
|
||||
ignore) rather than submit a short, mismatched batch — the RTL has no
|
||||
concept of a "partial octet."
|
||||
|
||||
**Real, deliberate, unchanged property**: the `WRITE_JOB` payload
|
||||
itself is byte-for-byte identical whether the octet requirement
|
||||
applies (N=16, N=8's own pair-of-4 requirement) or not (N=2's own
|
||||
simple pairing) — only the batch **size** firmware must submit before
|
||||
a batch reliably drains differs (8 for N=16, 2 for N=2). Always read
|
||||
`N_SLOTS` (§3.3) at startup and derive the real required batch size as
|
||||
`N_SLOTS / 2` rather than hardcoding 8.
|
||||
|
||||
---
|
||||
|
||||
## 5. Result readback
|
||||
|
||||
Results are **not** returned over SPI directly — they are written by
|
||||
`result_writeback.v` into DDR3 at the job's own `result_addr`, and
|
||||
must be read back via the existing `READ_MEM` (`0x02`) opcode. No new
|
||||
opcode exists or is needed for this.
|
||||
|
||||
**Real, exact addressing formula** (from `result_writeback.v`'s own
|
||||
header — `result_addr` here is the 26-bit value submitted in the
|
||||
job's own `WRITE_JOB` payload, §4.1):
|
||||
|
||||
```
|
||||
mem_addr (value) = result_addr[24:0] * 2
|
||||
mem_addr (node_id) = result_addr[24:0] * 2 + 1
|
||||
```
|
||||
|
||||
Two `READ_MEM` transactions per real result lane (one job produces
|
||||
**two** results, lane A and lane B — see `packed_pe.v`'s own real A/B
|
||||
lane structure):
|
||||
|
||||
- `mem_addr` above reads back a 16-bit word: low byte = the real INT8
|
||||
result value (`result_data`, zero-extended into the low 8 bits of
|
||||
the word); the other bytes are `8'h00`.
|
||||
- `mem_addr+1` reads back the real 16-bit `node_id` the job was
|
||||
submitted with — use this to match a result back to the job that
|
||||
produced it, since results may complete out of submission order
|
||||
across different groups.
|
||||
|
||||
**Real, disclosed firmware design question, not yet decided at the RTL
|
||||
level**: how the host knows a specific result is ready (vs. polling
|
||||
`READ_MEM` speculatively) is answered by §5.1 below (`data_ready_n`),
|
||||
but `data_ready_n` is a single, whole-device sticky flag, not
|
||||
per-job — firmware must track its own real outstanding-job count/set
|
||||
and read back results as they become plausible, not per-job-precise
|
||||
from the hardware alone.
|
||||
|
||||
### 5.1 `data_ready_n` — real, sticky, active-low interrupt
|
||||
|
||||
Real, board-level pin (D14). Lets firmware be interrupt-driven instead
|
||||
of polling `STATUS` in a loop.
|
||||
|
||||
- **Asserted (driven low)** when a real job/octet completes
|
||||
(`job_out_done`) **or** while `dir_error` is active.
|
||||
- **Sticky**: the job-completion contribution stays low even after the
|
||||
underlying completion pulse ends, until the host acknowledges by
|
||||
completing a real `STATUS` (`0x20`) **or** `REG_READ` of STATUS
|
||||
(`0x31`, reg `0x02`) transaction. A `REG_READ` of any *other*
|
||||
register does **not** acknowledge it.
|
||||
- The `dir_error` contribution is **live**, not latched — it clears
|
||||
the instant `dir_error` itself does (typically only after a real
|
||||
reset, since the Director has no in-band error-clear per §4.2).
|
||||
|
||||
**Real, recommended firmware pattern**: wire `data_ready_n` to an
|
||||
ESP32 GPIO interrupt; on falling edge, read `STATUS` (acknowledges the
|
||||
sticky latch and tells firmware whether it was a real completion or a
|
||||
`dir_error` condition), then read back whichever real results are now
|
||||
plausible.
|
||||
|
||||
---
|
||||
|
||||
## 6. Raw memory access (weight/activation preload, debug)
|
||||
|
||||
### 6.1 `0x01` WRITE_MEM / `0x02` READ_MEM
|
||||
|
||||
**Real, different address convention from `WRITE_JOB`**: these opcodes
|
||||
use `host_mem_bridge.v`'s own **16-bit-WORD** address space
|
||||
(`MEM_ADDR_WIDTH=25` bits), not the 26-bit byte-base convention
|
||||
`x_base`/`w_base`/`result_addr` use. Do not mix the two conventions.
|
||||
|
||||
- `WRITE_MEM`: `byte0:3 = addr[24:0]` (word address; `byte0`'s MSBs
|
||||
are `{7'b0, addr[24]}`), then `len_words` (2 bytes, right after
|
||||
addr) × 2 payload bytes, MSB-first per word. Each word is written
|
||||
via one real `mem_req`/`mem_ready` handshake before the next word's
|
||||
bytes are accepted — a real, sequential, per-word transaction, not a
|
||||
burst.
|
||||
- `READ_MEM`: same 6-byte header (4 addr + 2 `len_words`), 0 further
|
||||
MOSI payload; `2·len_words` response bytes clocked out on MISO
|
||||
starting at payload byte 7, MSB-first per word.
|
||||
|
||||
**Real, disclosed limitation** (not a bug, a deliberate, documented
|
||||
design choice, `host_mem_bridge.v`'s own header): this path halves the
|
||||
host's own reachable byte range for a given `ADDR_WIDTH`, since the
|
||||
underlying shared bus is 32-bit-word-native (EXP-0084's real DDR3
|
||||
widening) while this host-facing contract stays a fixed 16-bit word —
|
||||
`mem_addr`'s own LSB additionally selects which 16-bit half of the
|
||||
real 32-bit ctrl-bus word to target. Not currently a practical
|
||||
constraint at this project's real usage scale.
|
||||
|
||||
**Real use**: preloading weight/activation data into DDR3 before
|
||||
submitting jobs that reference it. See `docs/PHYSICAL_REALIZATION.md`
|
||||
§4 for the real, exact DDR3 memory layout convention (weight-per-layer
|
||||
addressing, 4-tiles-per-burst activation packing) firmware must follow
|
||||
when computing `x_base`/`w_base` values for `WRITE_JOB`.
|
||||
|
||||
### 6.2 `0x40` FLASH_XFER — config-flash relay
|
||||
|
||||
Raw, byte-for-byte SPI passthrough to the FPGA's own config flash. The
|
||||
FPGA relays every MOSI byte it receives onto the physical flash's own
|
||||
MOSI line, and relays whatever the flash returns back on MISO. **The
|
||||
FPGA knows nothing about SPI-NOR command semantics** — firmware is
|
||||
responsible for sending a real, complete flash command sequence,
|
||||
exactly as if directly wired to the flash.
|
||||
|
||||
**Real SPI-NOR opcodes** (Winbond W25Q32JV, verified against the
|
||||
actual datasheet):
|
||||
|
||||
| Opcode | Command |
|
||||
|---|---|
|
||||
| `0x06` | Write Enable |
|
||||
| `0x04` | Write Disable |
|
||||
| `0x05` | Read Status Register-1 (bit0=BUSY, bit1=WEL) |
|
||||
| `0x02` | Page Program |
|
||||
| `0x03` | Read Data |
|
||||
| `0x20` | Sector Erase (4KB) |
|
||||
| `0x52` | 32KB Block Erase |
|
||||
| `0xD8` | 64KB Block Erase |
|
||||
| `0xC7` / `0x60` | Chip Erase |
|
||||
|
||||
**Real, critical timing requirement** (measured via simulation,
|
||||
`spi_host_bridge_v3.v`'s own header, EXP-0077): a relayed byte's real
|
||||
flash response only becomes stable starting the transfer's own **byte
|
||||
N+2** — one extra host-clocked byte is not enough (`flash_spi_
|
||||
master.v`'s own internal transfer, ~640ns at 155.039MHz/DIV=4, starts
|
||||
only once byte N is fully received, overlapping byte N+1's own
|
||||
window). Firmware **must** clock **two** trailing dummy bytes at the
|
||||
end of a `FLASH_XFER` transaction to safely receive the final real
|
||||
response — one dummy byte is a real, previously-reproduced bug, not a
|
||||
hypothetical concern.
|
||||
|
||||
**Real update procedure**: after writing a new bitstream to the flash
|
||||
via `FLASH_XFER`, reconfigure either by pulsing `PROGRAM_B` externally
|
||||
(if the ESP32 has a GPIO wired to it — board-dependent), or (real,
|
||||
disclosed future work, not built) via an `ICAPE2`-based warm
|
||||
self-reconfiguration triggered over the same SPI bus.
|
||||
|
||||
---
|
||||
|
||||
## 7. Error handling
|
||||
|
||||
`dir_error` (readable via `STATUS`, contributes to `data_ready_n`) is
|
||||
the only real, hardware-visible error signal. It is a **latch**, not a
|
||||
transient flag — per `neural_director_grouped.v`'s own real, disclosed
|
||||
design (§4.2), there is **no in-band recovery**: once set (e.g. by a
|
||||
real octet-mismatch stall), only a hardware reset clears it.
|
||||
|
||||
**Real, recommended firmware policy**: treat `dir_error` as fatal to
|
||||
the current batch of in-flight jobs — issue a real reset (`RESET`
|
||||
opcode or `CONTROL` register bit0), re-establish `DEVICE_ID`/`STATUS`
|
||||
sanity (§2.1 steps 3–4), and re-submit any real jobs that were
|
||||
in-flight at the time of the error, since the Director's own internal
|
||||
queue state is not preserved across a reset.
|
||||
|
||||
---
|
||||
|
||||
## 8. Real, disclosed open items
|
||||
|
||||
- No ESP32-side firmware exists yet in this repository — this
|
||||
document is the specification, not an implementation.
|
||||
- JTAG bit-banging firmware (§2, path 1) is real, necessary, unbuilt
|
||||
software.
|
||||
- Real DDR3 calibration timing (§2.1 step 4) has not been measured on
|
||||
real hardware — only in simulation with `SIM_BYPASS_INIT_CAL`.
|
||||
- Whether/how the ESP32 firmware tracks outstanding jobs per-node_id
|
||||
(§5) — a real firmware design decision, not yet made.
|
||||
- `ICAPE2`-based warm self-reconfiguration (§6.2) is disclosed future
|
||||
work, not built.
|
||||
- `sys_rst`'s own real, permanent board pin location is still
|
||||
tentative (`docs/PHYSICAL_REALIZATION.md` §7) — confirm against the
|
||||
real board schematic before finalizing firmware GPIO assignments.
|
||||
@@ -165,26 +165,44 @@ these from an ESP32 doing real JTAG bit-banging (TAP state machine, IR/DR
|
||||
shifting) rather than a bench programmer — that firmware is separate,
|
||||
software-side work, not covered here.
|
||||
|
||||
## 3. Real timing signoff — N=8 in physical fabrication; N=2/N=8/N=16 all real, closed on branch `n16-timing-closure` (EXP-0095…0098)
|
||||
## 3. Real timing signoff — N=16 IS NOW THE REAL FABRICATION TARGET (EXP-0095…0098)
|
||||
|
||||
**Real, current, two-track status (2026-09-22)**: the **physical board
|
||||
currently being fabricated** uses **N=8** (`hardware/v3/rtl/
|
||||
n8_system_ddr3_top.v`, unmodified, EXP-0095/0096 signoff below) — this
|
||||
was the real, definitive decision made on 2026-09-21 after a real,
|
||||
measured N=4/8/16 timing curve found N=8 the largest configuration
|
||||
that closed cleanly at the time. Separately, on an isolated real
|
||||
development branch (`n16-timing-closure`, does NOT affect the board
|
||||
already in fabrication), a real fix (EXP-0097 — an extra real pipeline
|
||||
stage in `neural_processor_packed.v`'s own MAC datapath, see
|
||||
`docs/ARCHITECTURE_ANALYSIS.md` §5.6) has since **closed real timing
|
||||
for N=16 too, and improved N=2's and N=8's own margins** — the whole
|
||||
real product family (N=2/N=8/N=16) now closes with positive margin
|
||||
under that fix. Whether/when to adopt this fix for a future board
|
||||
revision (potentially N=16) is a real, open hardware decision, not an
|
||||
RTL blocker — see `docs/ARCHITECTURE_ANALYSIS.md` §5.6 for the full
|
||||
story.
|
||||
**UPDATE (2026-09-22): the user moved physical board fabrication to
|
||||
N=16.** `hardware/v3/rtl/n16_system_ddr3_top.v` (with the real EXP-0097
|
||||
MAC-pipeline fix) is now the real, current target — `v3-artix7` (the
|
||||
branch representing what is actually being built) has been fast-
|
||||
forward merged with the former `n16-timing-closure` branch, so both
|
||||
now point at the same real, consolidated state. **No physical board
|
||||
change is needed for this** — DDR3/SPI/flash/config pins are all
|
||||
package-level and identical regardless of `N_GROUPS` (confirmed real,
|
||||
not assumed, since N=2/N=8/N=16 all route through the exact same real
|
||||
XDC). Only the bitstream target changes.
|
||||
|
||||
### 3.0 N=8, in physical fabrication (`n8_system_ddr3_top.v`, EXP-0095/0096, unmodified core)
|
||||
N=8's own real signoff (§3.0 below) remains accurate and valid as a
|
||||
real, simpler fallback if ever needed, but is no longer what is
|
||||
physically being fabricated. N=2 (EXP-0088) remains the simplest real
|
||||
fallback below that. See `docs/ARCHITECTURE_ANALYSIS.md` §5.6 for the
|
||||
full real story of how N=16 was closed (EXP-0097/0098).
|
||||
|
||||
### 3.0 N=16, the current real fabrication target (`n16_system_ddr3_top.v`, EXP-0097/0098, with the MAC-pipeline fix)
|
||||
|
||||
| Metric | Value |
|
||||
|---|---|
|
||||
| DDR3 PHY clock (sys_clk_p/n) | **310.078 MHz** (3.225ns period) |
|
||||
| Compute domain clock (ui_clk/clk_pll_i) | **155.039 MHz** |
|
||||
| WNS (setup slack) | **+0.269 ns** — real, closed, 0 failing setup endpoints |
|
||||
| WHS (hold slack) | +0.026 ns |
|
||||
| Failing endpoints | 0 of 106087 (setup), 0 of 106084 (hold) |
|
||||
| LUTs used | 19903 / 63400 (31.39%) |
|
||||
| Registers used | 35409 / 126800 (27.93%) |
|
||||
| DSP48E1 used | 128 / 240 (53.33%) — 8 per PE × 16 real parallel PEs |
|
||||
| Real parallelism | **16 real parallel processing elements** |
|
||||
| Real functional verification | 32/32 PASS, real DDR3 model (`tb_n16_system_ddr3.v`) |
|
||||
|
||||
See `docs/ARCHITECTURE_ANALYSIS.md` §5.6.1 for the full real root-cause
|
||||
and fix story (the MAC-datapath pipeline split that closed this).
|
||||
|
||||
### 3.0-fallback N=8, real, valid fallback signoff (`n8_system_ddr3_top.v`, EXP-0095/0096, unmodified core)
|
||||
|
||||
Top-level RTL: `hardware/v3/rtl/n8_system_ddr3_top.v` — 2 groups × 4
|
||||
PEs, shared-weight-broadcast systolic architecture
|
||||
@@ -217,7 +235,7 @@ reach this result, EXP-0094/0095) after ANY further RTL change before
|
||||
trusting timing again — do not assume it still holds. This zero-margin
|
||||
result is exactly why EXP-0097's own pipeline fix (below) was pursued.
|
||||
|
||||
### 3.0.1 The real, consolidated family — EXP-0097/0098, branch `n16-timing-closure` (not yet on the fabricated board)
|
||||
### 3.0.1 The real, consolidated family — EXP-0097/0098 (N=16 is the fabrication target; N=2/N=8 kept real and valid as fallbacks)
|
||||
|
||||
A single real fix (`neural_processor_packed.v`, an extra real pipeline
|
||||
stage in the packed-MAC datapath — full real root-cause story in
|
||||
|
||||
Reference in New Issue
Block a user