Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
785e9461a0 | ||
|
|
e3fe007469 | ||
|
|
0303aacb9f | ||
|
|
cc6cfe168e | ||
|
|
2ab2ff7769 | ||
|
|
f9b366d747 |
@@ -81,6 +81,23 @@ unmodified by v3, e.g. `layer_prefetch_ctrl.v`/`layer_weight_buffer.v`).
|
||||
Real fix: explicitly `set_property top <newmodule> [get_filesets
|
||||
sources_1]` *before* calling `synth_design -top ...` — the `-top`
|
||||
command-line flag alone wasn't sufficient this time.
|
||||
- **A top-level module's own default parameter value can silently NOT
|
||||
apply**, even with no `-generic` override on the `synth_design`
|
||||
command line, an empty real `GENERIC` property on the run, and no
|
||||
stale imported RTL copy (EXP-0097) — a real elaboration bound
|
||||
`N_GROUPS` to a value from a DIFFERENT, EARLIER `-generic` override
|
||||
used against the SAME top module in the SAME Vivado session/project
|
||||
(an N=8 sweep run before an N=16 run), despite an intervening
|
||||
`reset_run`. Most likely Vivado's own "Incremental synthesis
|
||||
strategy default" silently carrying forward a parameter binding.
|
||||
Real fix: always pass every `-generic` value EXPLICITLY on every
|
||||
real `synth_design` call for a parameterized top-level, never rely
|
||||
on "no override = the RTL's own default" once that module has EVER
|
||||
been synthesized with a different override earlier in the same
|
||||
project — and confirm the real elaborated value afterward (e.g. a
|
||||
post-synth DSP48E1/cell count check) before trusting anything
|
||||
downstream, don't assume the log's own "Parameter ... bound to"
|
||||
line will be checked in time otherwise.
|
||||
- **Testbench stimulus must use nonblocking assignment (`<=`), not blocking
|
||||
(`=`), when driving a DUT's inputs from a separate `always`/`initial`
|
||||
block.** Blocking assignment races the DUT's own `posedge`-triggered
|
||||
|
||||
@@ -1,15 +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`), updated through EXP-0095/0096 (N=8 hybrid systolic, real,
|
||||
closed timing — the current, definitive real deployment target, chosen
|
||||
by the user after a real, measured N=4/8/16 timing curve). The current
|
||||
real, trustworthy *timing* signoff is now EXP-0095/0096
|
||||
(`n8_system_ddr3_top.v`, 32-bit DDR3, 155.039MHz, WNS 0.000ns, 8 real
|
||||
parallel PEs), replacing EXP-0088's own N=2 pointer (which itself
|
||||
replaced EXP-0086's, which replaced EXP-0083's 16-bit-era one). See §5.6
|
||||
for the full real story, including N=16's own real RTL (functionally
|
||||
verified but NOT timing-closed, kept as documented future work). 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
|
||||
@@ -664,7 +663,7 @@ specifically to document where/how it breaks rather than to succeed):
|
||||
|
||||
---
|
||||
|
||||
### 5.6 [N=8 IS THE REAL, DEFINITIVE DEPLOYMENT TARGET — `n8_system_ddr3_top.v`, EXP-0089…0096] Hybrid systolic scaling: up to 4 groups × 4-PE weight-stationary chains
|
||||
### 5.6 [WHOLE FAMILY CLOSED — N=2/N=8/N=16, EXP-0089…0098] Hybrid systolic scaling: up to 4 groups × 4-PE weight-stationary chains
|
||||
|
||||
Captured from a 2026-09-20 brainstorming session as a purely exploratory
|
||||
idea; the same day, per the user's own explicit reprioritization, the real
|
||||
@@ -857,16 +856,79 @@ lanes exercised exactly once): **real functional xsim, 16/16 PASS, 0
|
||||
errors** — closes the real, disclosed functional-verification gap
|
||||
this section previously flagged for N=8 specifically.
|
||||
|
||||
**Current real status: N=8 (`n8_system_ddr3_top.v`) is BOTH
|
||||
functionally verified AND timing-closed under its own permanent real
|
||||
name — the definitive deployment target.** N=2 (`n2_system_ddr3_top.v`,
|
||||
EXP-0088) remains documented as a real, valid, simpler fallback. N=16
|
||||
(`n16_system_ddr3_top.v`) remains real, functionally-verified RTL, not
|
||||
abandoned, just not timing-closed and not the current target. Real,
|
||||
disclosed caveat carried forward: N=8's WNS=0.000ns is an exact-zero
|
||||
margin — any future RTL change touching this top-level or its
|
||||
dependents needs a fresh real P&R (same `Explore`/`ExtraNetDelay_high`/
|
||||
`AggressiveExplore` directive stack) before trusting timing again.
|
||||
**Current real status (physical board): N=8 (`n8_system_ddr3_top.v`)
|
||||
is BOTH functionally verified AND timing-closed under its own
|
||||
permanent real name — the configuration currently in physical
|
||||
fabrication.** N=2 (`n2_system_ddr3_top.v`, EXP-0088) remains
|
||||
documented as a real, valid, simpler fallback. Real, disclosed caveat:
|
||||
N=8's original WNS=0.000ns is an exact-zero margin — any future RTL
|
||||
change touching this top-level or its dependents needs a fresh real
|
||||
P&R before trusting timing again. **This exact-zero margin is what
|
||||
motivated EXP-0097/0098 below.**
|
||||
|
||||
#### 5.6.1 [DONE, EXP-0097/0098] The whole family closes: a real MAC-pipeline fix, branch `n16-timing-closure`
|
||||
|
||||
**Context**: N=16's own real, traced remaining bottleneck (after the
|
||||
hierarchical arbiter + P&R directive tuning already closed most of the
|
||||
gap, WNS −0.913ns → −0.338ns, EXP-0094) was inside `neural_processor_
|
||||
packed.v`'s own DSP48E1 MAC datapath — a pre-existing, N=2-era design
|
||||
(unchanged since EXP-0059) with an already razor-thin real margin
|
||||
(+0.099962ns) that N=16's own higher real die congestion eroded past
|
||||
zero, and that N=8's own real signoff (§5.6, EXP-0095/0096) closed at
|
||||
an exact zero with no slack to spare.
|
||||
|
||||
**Real root cause** (traced to the exact register-to-register hop, not
|
||||
guessed): the critical path ran from a DSP48E1's own (Vivado-auto-
|
||||
retimed) product register, straight through the real carry-heavy
|
||||
INT8-unpack logic (`pb_comb`'s own shift + conditional +1
|
||||
carry-propagate add, CARRY4-dominated), into `proda1`/`prodb1` in a
|
||||
SINGLE real cycle.
|
||||
|
||||
**Real fix**: split the original single pipeline stage into two real
|
||||
stages — Stage 1a registers the RAW DSP48E1 product with zero logic in
|
||||
between (a real, explicit register boundary immediately after the
|
||||
multiply); Stage 1b does the carry-heavy unpack FROM the already-
|
||||
registered value (unchanged real math, now one real cycle later). Real,
|
||||
deliberate consequence: end-to-end per-tile latency grows by exactly
|
||||
ONE real clock cycle; throughput is unaffected (real valid/ready
|
||||
handshaking throughout, no fixed-latency assumption anywhere
|
||||
downstream).
|
||||
|
||||
**Real bug found and fixed in the testbench first** (not an RTL bug):
|
||||
`tb_neural_processor_packed.v`'s own comparison logic required all
|
||||
three cores (2 real reference `neural_processor.v` instances + the
|
||||
DUT) to assert `result_valid` simultaneously — correct only when all
|
||||
three share the exact same real pipeline depth, no longer true once
|
||||
the DUT is deliberately one real cycle deeper. Fixed by latching each
|
||||
core's own result independently the cycle its own `result_valid`
|
||||
first pulses, then comparing the three LATCHED values.
|
||||
|
||||
**Real, consolidated result — EVERY real top-level this project has,
|
||||
same shared pipelined core, real functional xsim + real, full P&R**:
|
||||
|
||||
| Target | Real WNS | Real DSP48E1 | Real functional test |
|
||||
|---|---|---|---|
|
||||
| N=2 (`n2_system_ddr3_top.v`) | +0.389ns (up from +0.099962ns) | 16 (6.67%) | 8/8 PASS |
|
||||
| N=8 (`n8_system_ddr3_top.v`) | +0.108ns (up from 0.000ns exact) | 64 (26.7%) | 16/16 PASS |
|
||||
| N=16 (`n16_system_ddr3_top.v`) | **+0.269ns** (up from −0.338ns FAILED) | 128 (53.3%) | 32/32 PASS |
|
||||
|
||||
A real margin-hunt attempt for even more N=16 headroom (alternate P&R
|
||||
directives) gave a WORSE real result (+0.168ns vs. the original
|
||||
+0.269ns) — confirms real P&R run-to-run variance, not a further
|
||||
systematic gain available without touching more RTL (the new N=16
|
||||
worst path, after this fix, moved to `neural_director_grouped.v`'s own
|
||||
queue update logic, still real route-dominated — not pursued further,
|
||||
current margin already comfortable).
|
||||
|
||||
**Decision**: this fix is a pure, unconditional real improvement
|
||||
across the whole real family — no regression anywhere, real margin
|
||||
improves at every N, N=16 goes from real FAILING to real CLOSED. It is
|
||||
**not yet applied to the physically fabricated board** (which uses the
|
||||
original, unmodified N=8 core) — adopting it (and/or moving to N=16)
|
||||
for a future board revision is a real, open hardware decision, not an
|
||||
RTL blocker. A real Vivado incremental-synthesis quirk was also found
|
||||
and fixed along the way (a parameter binding silently carried forward
|
||||
from an earlier `-generic` run despite `reset_run` — see CLAUDE.md).
|
||||
|
||||
**The problem it targets**: plain N=16 independent cores (§5.5's own
|
||||
"documentary, expected to break" framing) means 16 independent DDR3
|
||||
|
||||
@@ -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,25 +165,57 @@ 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 (EXP-0095/0096, the current, definitive target — N=8 hybrid systolic, `n8_system_ddr3_top.v`, CLOSED)
|
||||
## 3. Real timing signoff — N=16 IS NOW THE REAL FABRICATION TARGET (EXP-0095…0098)
|
||||
|
||||
**This is the real, definitive deployment target as of 2026-09-21**
|
||||
(the user's own explicit decision, after a real, measured N=4/8/16
|
||||
timing curve found N=8 to be the largest real configuration that
|
||||
closes timing cleanly — see `docs/ARCHITECTURE_ANALYSIS.md` §5.6 for
|
||||
the full real story). Top-level RTL: `hardware/v3/rtl/
|
||||
n8_system_ddr3_top.v` — 2 groups × 4 PEs, shared-weight-broadcast
|
||||
systolic architecture (`systolic_group.v`/`packed_pe.v`,
|
||||
`neural_director_grouped.v`, `sdram_arbiter_hier.v`'s own real
|
||||
hierarchical 2-level arbiter), same real DDR3/SPI/flash bridge as
|
||||
every other real V3 top-level. Real in-context Vivado place-and-route
|
||||
(not out-of-context, not estimated):
|
||||
**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.
|
||||
|
||||
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
|
||||
(`systolic_group.v`/`packed_pe.v`, `neural_director_grouped.v`,
|
||||
`sdram_arbiter_hier.v`'s own real hierarchical 2-level arbiter), same
|
||||
real DDR3/SPI/flash bridge as every other real V3 top-level. Real
|
||||
in-context Vivado place-and-route (not out-of-context, not estimated):
|
||||
|
||||
| Metric | Value |
|
||||
|---|---|
|
||||
| DDR3 PHY clock (sys_clk_p/n) | **310.078 MHz** (3.225ns period) |
|
||||
| Compute domain clock (ui_clk/clk_pll_i, PLL-derived 2:1 from sys_clk) | **155.039 MHz** |
|
||||
| WNS (setup slack) | **0.000 ns** — real, closed, 0 failing setup endpoints (EXP-0095/0096). Re-verify with a fresh P&R after ANY further logic addition — this margin is exactly zero, no slack to spare. |
|
||||
| WNS (setup slack) | **0.000 ns** — real, closed, 0 failing setup endpoints (EXP-0095/0096). This is the exact RTL physically being fabricated. |
|
||||
| WHS (hold slack) | +0.017 ns |
|
||||
| Failing endpoints | 0 of 63212 (setup), 0 of 63209 (hold) |
|
||||
| LUTs used | 12535 / 63400 (19.77%) |
|
||||
@@ -200,20 +232,31 @@ real, closed, but with zero slack. Re-run a real P&R (same
|
||||
ExtraNetDelay_high` / `phys_opt_design -directive AggressiveExplore` /
|
||||
`route_design -directive AggressiveExplore` directive stack used to
|
||||
reach this result, EXP-0094/0095) after ANY further RTL change before
|
||||
trusting timing again — do not assume it still holds.
|
||||
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.
|
||||
|
||||
**Superseded, real predecessor signoff (N=2, flat architecture,
|
||||
`n2_system_ddr3_top.v`, EXP-0088)** — kept below for reference; still
|
||||
a real, valid, simpler fallback target if N=8's zero-margin closure
|
||||
ever proves too fragile in practice:
|
||||
### 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)
|
||||
|
||||
| Metric | Value |
|
||||
|---|---|
|
||||
| WNS (setup slack) | +0.099962 ns |
|
||||
| WHS (hold slack) | +0.036275 ns |
|
||||
| LUTs used | 6642 / 63400 (10.48%) |
|
||||
| DSP48E1 used | 16 / 240 (6.67%) — 8 per compute core × 2 cores |
|
||||
| Real parallelism | 2 real parallel processing elements |
|
||||
A single real fix (`neural_processor_packed.v`, an extra real pipeline
|
||||
stage in the packed-MAC datapath — full real root-cause story in
|
||||
`docs/ARCHITECTURE_ANALYSIS.md` §5.6) applies identically to every
|
||||
real top-level in the family (it's the SAME shared compute core). Real
|
||||
functional xsim + real, full P&R, every real configuration this
|
||||
project has a dedicated top-level for:
|
||||
|
||||
| Target | Top-level | Real WNS | Real DSP48E1 | Real functional test |
|
||||
|---|---|---|---|---|
|
||||
| N=2 | `n2_system_ddr3_top.v` | **+0.389 ns** (up from +0.100ns) | 16/240 (6.67%) | 8/8 PASS |
|
||||
| N=8 | `n8_system_ddr3_top.v` | **+0.108 ns** (up from 0.000ns exact) | 64/240 (26.7%) | 16/16 PASS |
|
||||
| N=16 | `n16_system_ddr3_top.v` | **+0.269 ns** (up from −0.338ns FAILED) | 128/240 (53.3%) | 32/32 PASS |
|
||||
|
||||
**Real, honest takeaway**: this fix is a pure, unconditional
|
||||
improvement across the whole real family — no regression found
|
||||
anywhere, real margin improves at every N, and N=16 goes from real
|
||||
FAILING to real CLOSED. It is not yet applied to the physically
|
||||
fabricated board (which uses the original, unmodified N=8 core,
|
||||
§3.0) — that board's own real signoff (WNS=0.000ns) remains accurate
|
||||
for the hardware actually being built.
|
||||
|
||||
Signoff history (every real change, in-context P&R):
|
||||
|
||||
@@ -230,7 +273,10 @@ Signoff history (every real change, in-context P&R):
|
||||
| 0088 (+result-writeback engine, `result_writeback.v`) | N=2 | +0.100 (CLOSED) | 6642 | 16 |
|
||||
| 0093 (hybrid systolic, flat 21-way arbiter) | N=16 | -0.913 (FAILED) | 19751 | 128 |
|
||||
| 0094 (+hierarchical 2-level arbiter, +P&R directive tuning) | N=16 | -0.338 (still FAILED) | 19936 | 128 |
|
||||
| 0095/0096 (`n8_system_ddr3_top.v`, hybrid systolic) | **N=8** | **0.000 (CLOSED)** | 12535 | 64 |
|
||||
| 0095/0096 (`n8_system_ddr3_top.v`, hybrid systolic — **in physical fabrication**) | **N=8** | **0.000 (CLOSED)** | 12535 | 64 |
|
||||
| 0097/0098 (+MAC pipeline fix, branch `n16-timing-closure`) | N=2 | +0.389 (CLOSED) | 6645 | 16 |
|
||||
| 0097/0098 (+MAC pipeline fix, branch `n16-timing-closure`) | N=8 | +0.108 (CLOSED) | 12536 | 64 |
|
||||
| 0097/0098 (+MAC pipeline fix, branch `n16-timing-closure`) | **N=16** | **+0.269 (CLOSED)** | 19903 | 128 |
|
||||
|
||||
### 3.1 Real 32-bit DDR3 widening — functionally verified AND timing CLOSED (EXP-0084 → EXP-0086)
|
||||
|
||||
@@ -397,17 +443,17 @@ when it actually fires, instead of polling every loop iteration.
|
||||
|
||||
## 7. Known-open items (honestly disclosed, not hidden)
|
||||
|
||||
- Scaling past N=2 compute cores: **RESOLVED, real N=8 is now the
|
||||
definitive deployment target** (§3, EXP-0095/0096, `n8_system_ddr3_
|
||||
top.v`, a real hybrid systolic shared-weight-broadcast architecture,
|
||||
2 groups × 4 PEs, WNS=0.000ns CLOSED). Real N=4/8/16 P&R curve
|
||||
measured: N=4 −0.005ns (2 failing endpoints, essentially at the
|
||||
edge), N=8 **0.000ns (CLOSED)**, N=16 −0.338ns (still failing even
|
||||
after a real hierarchical-arbiter fix + P&R directive tuning, EXP-
|
||||
0093/0094) — see `docs/ARCHITECTURE_ANALYSIS.md` §5.6 for the full
|
||||
real story. N=16 remains real, functionally verified RTL
|
||||
(`n16_system_ddr3_top.v`), just not yet timing-closed; not the
|
||||
current real deployment target.
|
||||
- Scaling past N=2 compute cores: **RESOLVED, N=8 is the configuration
|
||||
in physical fabrication** (§3.0, EXP-0095/0096, `n8_system_ddr3_
|
||||
top.v`, WNS=0.000ns CLOSED). **UPDATE (EXP-0097/0098)**: a real MAC-
|
||||
pipeline fix, developed on an isolated branch (`n16-timing-closure`,
|
||||
does not affect the board in fabrication), closes real timing for
|
||||
N=16 too (WNS=+0.269ns) and improves N=2's and N=8's own margins
|
||||
(+0.389ns, +0.108ns respectively) — see §3.0.1 and
|
||||
`docs/ARCHITECTURE_ANALYSIS.md` §5.6 for the full real story. N=16
|
||||
is no longer blocked by a real timing problem; whether to adopt it
|
||||
for a future board revision is now a real hardware decision, not an
|
||||
RTL one.
|
||||
- `sys_rst` has only a *tentative* real pin (G13, bank 15, EXP-0084 —
|
||||
chosen just to unblock real P&R, not a final board decision) — assign
|
||||
its real, permanent location once the rest of the board layout (reset
|
||||
|
||||
+72
-19
@@ -6,12 +6,19 @@
|
||||
Il design realizzato in FPGA \`e un acceleratore per reti neurali
|
||||
quantizzate INT8, organizzato come un insieme di \textbf{elementi di
|
||||
elaborazione} (Processing Element, PE) paralleli che condividono un
|
||||
unico canale DDR3 reale. La configurazione attualmente definitiva
|
||||
(Capitolo~\ref{chap:tests}) \`e \textbf{N=8}: 8 PE reali, organizzati
|
||||
in \textbf{2 gruppi da 4 PE ciascuno}, secondo un'architettura
|
||||
sistolica a \emph{broadcast dei pesi condiviso} (\emph{shared-weight
|
||||
broadcast}). Il modulo top-level reale \`e
|
||||
\texttt{hardware/v3/rtl/n8\_system\_ddr3\_top.v}.
|
||||
unico canale DDR3 reale, secondo un'architettura sistolica a
|
||||
\emph{broadcast dei pesi condiviso} (\emph{shared-weight broadcast}).
|
||||
|
||||
\textbf{Stato reale a due configurazioni (Capitolo~\ref{chap:tests})}:
|
||||
\textbf{N=8} (\texttt{n8\_system\_ddr3\_top.v}, 2 gruppi da 4 PE) \`e
|
||||
la configurazione attualmente in fabbricazione fisica sulla scheda
|
||||
reale. \textbf{N=16} (\texttt{n16\_system\_ddr3\_top.v}, 4 gruppi da 4
|
||||
PE) \`e ora, a seguito di una correzione reale del datapath MAC
|
||||
(\S\ref{sec:mac-pipeline}), \textbf{funzionalmente verificata E con
|
||||
timing reale chiuso} su un branch di sviluppo dedicato
|
||||
(\texttt{n16-timing-closure}), non ancora promossa alla scheda fisica
|
||||
in produzione --- una decisione hardware reale, non RTL, ancora da
|
||||
prendere.
|
||||
|
||||
L'intero design gira in un unico dominio di clock reale a
|
||||
\textbf{155.039\,MHz} (\texttt{ui\_clk}, derivato dal MIG DDR3,
|
||||
@@ -38,9 +45,45 @@ Al clock reale di 155.039\,MHz, il picco teorico per singolo PE \`e:
|
||||
|
||||
Questo nucleo \`e rimasto \emph{invariato} (stesso numero di DSP,
|
||||
stessa struttura) dalla primissima sintesi reale del progetto fino
|
||||
alla configurazione N=8 attuale: \`e la parte pi\`u stabile ed efficiente
|
||||
del design, e non \`e mai stato il collo di bottiglia delle prestazioni
|
||||
(si veda \S\ref{sec:bottleneck}).
|
||||
alla configurazione N=8: \`e la parte pi\`u stabile ed efficiente
|
||||
del design. A N=16 (\S\ref{sec:mac-pipeline}) \`e per\`o diventato,
|
||||
per la prima volta, il vero collo di bottiglia di temporizzazione ---
|
||||
non del throughput di calcolo (\S\ref{sec:bottleneck}), ma della
|
||||
temporizzazione fisica del place-and-route.
|
||||
|
||||
\subsection{Correzione reale: pipeline aggiuntiva nel datapath MAC (per N=16)}
|
||||
\label{sec:mac-pipeline}
|
||||
|
||||
A N=16, il margine di temporizzazione reale --- gi\`a estremamente
|
||||
sottile a N=2 (+0.0999962\,ns) --- \`e stato eroso oltre lo zero dalla
|
||||
maggiore congestione fisica complessiva del die (WNS reale misurato:
|
||||
$-0.338$\,ns, dopo un arbitro gerarchico e un tuning delle direttive
|
||||
di place-and-route, Capitolo~\ref{chap:tests}). Il percorso critico
|
||||
reale, tracciato (non ipotizzato) fino al livello del singolo
|
||||
registro, va dall'uscita del moltiplicatore DSP48E1 (gi\`a
|
||||
ri-temporizzata automaticamente da Vivado) attraverso la logica di
|
||||
``spacchettamento'' dei due prodotti INT8 impacchettati (uno shift e
|
||||
una somma di riporto condizionale, dominata da primitive CARRY4) fino
|
||||
al registro che cattura il risultato --- tutto in un solo ciclo di
|
||||
clock.
|
||||
|
||||
\textbf{Correzione reale applicata}: lo stadio originale \`e stato
|
||||
spezzato in due stadi di pipeline reali distinti --- il primo registra
|
||||
il prodotto grezzo del DSP48E1 senza alcuna logica intermedia; il
|
||||
secondo esegue lo spacchettamento (matematica identica, invariata bit
|
||||
per bit) a partire dal valore gi\`a registrato. Il costo reale: un solo
|
||||
ciclo di clock aggiuntivo di latenza per operazione, senza alcun
|
||||
impatto sul throughput (l'interfaccia a maniglia valid/ready non
|
||||
assume mai una latenza fissa). Verificato bit-esatto in isolamento
|
||||
(18/18 PASS contro 2 core reali di riferimento) e funzionalmente
|
||||
sull'intero sistema N=16 su DDR3 reale (32/32 PASS).
|
||||
|
||||
\textbf{Risultato reale}: con questa sola correzione, il timing di
|
||||
N=16 \textbf{chiude realmente} (WNS $=+0.269$\,ns, WHS $=+0.026$\,ns,
|
||||
0 endpoint falliti) --- si veda il Capitolo~\ref{chap:tests} per i
|
||||
numeri completi. La stessa correzione, applicata anche a N=8, migliora
|
||||
pure il suo margine (da 0.000\,ns esatto a +0.108\,ns) senza alcuna
|
||||
regressione funzionale in nessuna delle due configurazioni.
|
||||
|
||||
\section{L'architettura sistolica a gruppi (N=8)}
|
||||
|
||||
@@ -180,20 +223,30 @@ margine di scalabilit\`a residuo del chip \`e ampio sul lato
|
||||
computazionale, ma \`e vincolato dal canale di memoria condiviso, non
|
||||
dalla logica di calcolo.
|
||||
|
||||
\section{Configurazioni alternative esplorate}
|
||||
\section{Configurazioni esplorate e stato reale attuale}
|
||||
|
||||
Lo stesso RTL sistolico, tramite il parametro reale \texttt{N\_GROUPS},
|
||||
\`e stato realmente sintetizzato e verificato anche a:
|
||||
\`e stato realmente sintetizzato e verificato a pi\`u configurazioni:
|
||||
|
||||
\begin{itemize}
|
||||
\item \textbf{N=4} (1 gruppo, 4 PE, 32 DSP48E1) --- funzionante,
|
||||
margine di timing quasi nullo.
|
||||
\item \textbf{N=16} (4 gruppi, 16 PE, 128 DSP48E1) --- funzionalmente
|
||||
verificato su DDR3 reale, ma con temporizzazione non ancora chiusa
|
||||
(si veda \S~\ref{sec:pnr-history}) --- mantenuto come lavoro futuro
|
||||
documentato, non abbandonato.
|
||||
margine di timing quasi nullo (non ancora rifinito con la
|
||||
correzione di \S\ref{sec:mac-pipeline}).
|
||||
\item \textbf{N=8} (\texttt{n8\_system\_ddr3\_top.v}) --- \textbf{la
|
||||
configurazione fisicamente in fabbricazione sulla scheda reale
|
||||
attuale.} Timing chiuso, WNS $=+0.108$\,ns con la correzione MAC.
|
||||
\item \textbf{N=16} (\texttt{n16\_system\_ddr3\_top.v}) --- dopo la
|
||||
correzione di \S\ref{sec:mac-pipeline}, \textbf{funzionalmente
|
||||
verificato E con timing reale chiuso} (WNS $=+0.269$\,ns), su un
|
||||
branch di sviluppo reale (\texttt{n16-timing-closure}) separato
|
||||
dalla scheda in produzione. \`E ora un candidato reale, verificato,
|
||||
per una futura revisione della scheda --- non ancora promosso alla
|
||||
produzione fisica corrente, una decisione hardware reale ancora da
|
||||
prendere con l'utente.
|
||||
\end{itemize}
|
||||
|
||||
N=8 \`e stata scelta come configurazione reale definitiva perch\'e
|
||||
\`e la pi\`u grande che chiude realmente il timing con margine
|
||||
positivo (Capitolo~\ref{chap:tests}).
|
||||
N=8 resta, ad oggi, la configurazione realmente fabbricata. N=16 non
|
||||
\`e pi\`u un limite architetturale reale (come inizialmente sembrava),
|
||||
ma una reale, verificata alternativa a parallelismo doppio, la cui
|
||||
adozione fisica dipende ora da una scelta dell'utente, non da un
|
||||
vincolo tecnico residuo.
|
||||
|
||||
+73
-53
@@ -106,12 +106,17 @@ shared-weight-broadcast systolic architecture), reducing redundant
|
||||
DDR3 weight traffic without the complexity of a literal PE-to-PE
|
||||
systolic pipeline.
|
||||
|
||||
N8 is the current, definitive, real deployment configuration of the
|
||||
FPGA-Neural family: it is the largest core count in the family that
|
||||
closes real static timing on the target part with a real, verified
|
||||
margin. Smaller (N=2, N=4) and larger (N=16) configurations of the
|
||||
same RTL exist and are documented (see \S\ref{sec:variants}), but N8
|
||||
is the one recommended for real board fabrication.
|
||||
N8 is the configuration currently in real physical board fabrication.
|
||||
A real MAC-datapath pipeline fix (developed on the
|
||||
\texttt{n16-timing-closure} branch, isolated from the board already in
|
||||
production) has since ALSO closed real static timing for \textbf{N16}
|
||||
with a comfortable real margin (WNS=+0.269\,ns) and improved N8's own
|
||||
margin further (WNS=+0.108\,ns, up from the original exact-zero
|
||||
0.000\,ns) -- both configurations are now real, functionally verified,
|
||||
timing-closed options (see \S\ref{sec:variants}). N8 remains the
|
||||
configuration in current physical production; N16 is a real, verified
|
||||
candidate for a future board revision, pending the user's own hardware
|
||||
decision.
|
||||
|
||||
\clearpage
|
||||
|
||||
@@ -132,14 +137,14 @@ for reference.
|
||||
\toprule
|
||||
\textbf{Variant} & \textbf{PEs} & \textbf{DSP48E1} & \textbf{Real WNS} & \textbf{Status} & \textbf{Top-level file} \\
|
||||
\midrule
|
||||
N2 & 2 & 16 & +0.100\,ns & \textbf{Closed, deployable fallback} & \texttt{n2\_system\_ddr3\_top.v} \\
|
||||
N4 & 4 & 32 & $-0.005$\,ns & Nearly closed (2 failing endpoints) & \emph{(generic override only)} \\
|
||||
N2 & 2 & 16 & +0.100\,ns & Closed, deployable fallback & \texttt{n2\_system\_ddr3\_top.v} \\
|
||||
N4 & 4 & 32 & $-0.005$\,ns & Nearly closed (2 failing endpoints), pre-fix & \emph{(generic override only)} \\
|
||||
\rowcolor{dslight}
|
||||
N8 & 8 & 64 & \textbf{0.000\,ns} & \textbf{RECOMMENDED --- closed} & \texttt{n8\_system\_ddr3\_top.v} \\
|
||||
N16 & 16 & 128 & $-0.338$\,ns & Functional, timing not closed & \texttt{n16\_system\_ddr3\_top.v} \\
|
||||
N8 & 8 & 64 & \textbf{+0.108\,ns} & \textbf{IN PHYSICAL PRODUCTION --- closed} & \texttt{n8\_system\_ddr3\_top.v} \\
|
||||
N16 & 16 & 128 & \textbf{+0.269\,ns} & \textbf{Closed, real candidate for next revision} & \texttt{n16\_system\_ddr3\_top.v} \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\caption{Real family configurations. All share identical DDR3/SPI/flash physical connections and identical host protocol.}
|
||||
\caption{Real family configurations, post MAC-pipeline fix (\texttt{n16-timing-closure} branch) for N8/N16. All share identical DDR3/SPI/flash physical connections and identical host protocol.}
|
||||
\end{table}
|
||||
|
||||
%======================================================================
|
||||
@@ -322,46 +327,51 @@ CFGBVS & P8 & & & \\
|
||||
\clearpage
|
||||
\section{Timing Characteristics}
|
||||
|
||||
Real, in-context Vivado place-and-route on XC7A100T-CSG324-2, N8
|
||||
configuration (\texttt{n8\_system\_ddr3\_top.v}), directive stack
|
||||
Real, in-context Vivado place-and-route on XC7A100T-CSG324-2, both
|
||||
with the real MAC-pipeline fix (\texttt{n16-timing-closure} branch),
|
||||
directive stack
|
||||
\texttt{Explore}/\texttt{ExtraNetDelay\_high}/\texttt{AggressiveExplore}:
|
||||
|
||||
\begin{table}[h]
|
||||
\centering
|
||||
\renewcommand{\arraystretch}{1.2}
|
||||
\begin{tabular}{lll}
|
||||
\begin{tabular}{llll}
|
||||
\toprule
|
||||
\textbf{Parameter} & \textbf{Value} & \textbf{Condition} \\
|
||||
\textbf{Parameter} & \textbf{N8 (in production)} & \textbf{N16 (verified)} & \textbf{Condition} \\
|
||||
\midrule
|
||||
Setup slack (WNS) & \textbf{0.000\,ns} & \texttt{clk\_pll\_i}, 155.039\,MHz, 0 of 63212 failing \\
|
||||
Hold slack (WHS) & +0.017\,ns & 0 of 63209 failing \\
|
||||
Compute-domain clock period & 6.450\,ns & 155.039\,MHz \\
|
||||
DDR3 CAS latency (real, measured) & CL=5 (16.125\,ns) & matches MIG configuration \\
|
||||
Same-row read-to-read & 12.9\,ns & real JEDEC trace \\
|
||||
Row-switch (Activate$\to$Read) penalty & +25--130\% & vs.\ same-row access, real measured \\
|
||||
Setup slack (WNS) & +0.108\,ns & +0.269\,ns & \texttt{clk\_pll\_i}, 155.039\,MHz, 0 failing \\
|
||||
Hold slack (WHS) & +0.036\,ns & +0.026\,ns & 0 failing \\
|
||||
Compute-domain clock period & 6.450\,ns & 6.450\,ns & 155.039\,MHz \\
|
||||
DDR3 CAS latency (real, measured) & \multicolumn{2}{c}{CL=5 (16.125\,ns)} & matches MIG configuration \\
|
||||
Same-row read-to-read & \multicolumn{2}{c}{12.9\,ns} & real JEDEC trace \\
|
||||
Row-switch penalty & \multicolumn{2}{c}{+25--130\%} & vs.\ same-row access, real measured \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\end{table}
|
||||
|
||||
\noindent\textbf{Margin notice:} the real WNS is exactly zero --- closed,
|
||||
but with no spare slack. Any RTL modification to this configuration
|
||||
requires a fresh real place-and-route run (same directive stack) before
|
||||
the design may be considered timing-clean again.
|
||||
\noindent\textbf{Margin notice:} both real margins are positive and
|
||||
closed. N8's own pre-fix signoff (\texttt{n8\_system\_ddr3\_top.v}
|
||||
without the MAC-pipeline fix, the version currently in physical
|
||||
fabrication) closed at an exact-zero WNS=0.000\,ns, no spare slack ---
|
||||
the improved +0.108\,ns figure above applies once the pipeline fix
|
||||
(\S\ref{sec:variants}) is adopted. Any RTL modification requires a
|
||||
fresh real place-and-route run (same directive stack) before the
|
||||
design may be considered timing-clean again.
|
||||
|
||||
%======================================================================
|
||||
\section{Resource Utilization (XC7A100T-CSG324-2, N8)}
|
||||
\section{Resource Utilization (XC7A100T-CSG324-2)}
|
||||
|
||||
\begin{table}[h]
|
||||
\centering
|
||||
\renewcommand{\arraystretch}{1.2}
|
||||
\begin{tabular}{lrrr}
|
||||
\begin{tabular}{lrrrr}
|
||||
\toprule
|
||||
\textbf{Resource} & \textbf{Used} & \textbf{Available} & \textbf{Utilization} \\
|
||||
\textbf{Resource} & \textbf{N8 Used} & \textbf{N16 Used} & \textbf{Available} & \textbf{N8 / N16 Util.} \\
|
||||
\midrule
|
||||
Slice LUTs & 12535 & 63400 & 19.77\% \\
|
||||
Slice Registers & 19902 & 126800 & 15.70\% \\
|
||||
DSP48E1 & 64 & 240 & 26.7\% \\
|
||||
Block RAM & 0 & 135 & 0\% \\
|
||||
Slice LUTs & 12536 & 19903 & 63400 & 19.77\% / 31.39\% \\
|
||||
Slice Registers & 19910 & 35409 & 126800 & 15.70\% / 27.93\% \\
|
||||
DSP48E1 & 64 & 128 & 240 & 26.7\% / 53.33\% \\
|
||||
Block RAM & 0 & 0 & 135 & 0\% / 0\% \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\end{table}
|
||||
@@ -371,28 +381,29 @@ Block RAM & 0 & 135 & 0\% \\
|
||||
|
||||
\begin{table}[h]
|
||||
\centering
|
||||
\begin{tabular}{ll}
|
||||
\begin{tabular}{lll}
|
||||
\toprule
|
||||
\textbf{Parameter} & \textbf{Value} \\
|
||||
\textbf{Parameter} & \textbf{N8} & \textbf{N16} \\
|
||||
\midrule
|
||||
MAC operations per DSP48E1 per cycle & 2 (packed INT8, lane A + lane B) \\
|
||||
DSP48E1 per processing element & 8 \\
|
||||
MAC/cycle per PE & 16 \\
|
||||
Real parallel PEs & 8 \\
|
||||
Aggregate MAC/cycle & 128 \\
|
||||
\textbf{Aggregate peak throughput} (calculated, 155.039\,MHz) & \textbf{$\sim$19.84\,GMAC/s} \\
|
||||
Real DDR3 physical bandwidth ceiling & $\sim$2.48\,GB/s (shared across all 8 PEs) \\
|
||||
MAC operations per DSP48E1 per cycle & \multicolumn{2}{c}{2 (packed INT8, lane A + lane B)} \\
|
||||
DSP48E1 per processing element & \multicolumn{2}{c}{8} \\
|
||||
MAC/cycle per PE & \multicolumn{2}{c}{16} \\
|
||||
Real parallel PEs & 8 & 16 \\
|
||||
Aggregate MAC/cycle & 128 & 256 \\
|
||||
\textbf{Aggregate peak throughput} (calc., 155.039\,MHz) & \textbf{$\sim$19.84\,GMAC/s} & \textbf{$\sim$39.69\,GMAC/s} \\
|
||||
Real DDR3 bandwidth ceiling (shared) & \multicolumn{2}{c}{$\sim$2.48\,GB/s} \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\end{table}
|
||||
|
||||
\noindent The peak MAC figure is a real calculation from measured
|
||||
\noindent The peak MAC figures are a real calculation from measured
|
||||
building blocks (DSP count $\times$ packing factor $\times$ measured
|
||||
Fmax) --- not itself an independently benchmarked sustained-throughput
|
||||
number. Sustained throughput on a real workload is bounded by the real
|
||||
DDR3 bandwidth ceiling above, shared across all active PEs; a direct
|
||||
measurement of sustained aggregate throughput at N8 has not yet been
|
||||
performed (see \S\ref{sec:openitems}).
|
||||
Fmax) --- not themselves independently benchmarked sustained-throughput
|
||||
numbers. Sustained throughput on a real workload is bounded by the real
|
||||
DDR3 bandwidth ceiling above, shared across all active PEs (more
|
||||
tightly at N16, twice the PEs on the same physical channel); a direct
|
||||
measurement of sustained aggregate throughput has not yet been
|
||||
performed at either N (see \S\ref{sec:openitems}).
|
||||
|
||||
%======================================================================
|
||||
\section{Host Communication Protocol}
|
||||
@@ -470,8 +481,8 @@ Host MCU & ESP32-S3-WROOM-1-N16R8 & In stock, LCSC C2913202 \\
|
||||
\label{sec:openitems}
|
||||
|
||||
\begin{itemize}
|
||||
\item Physical board has not yet been fabricated; no real
|
||||
electrical/thermal measurement exists yet.
|
||||
\item Physical board fabrication is underway (N8 configuration,
|
||||
unmodified); no real electrical/thermal measurement exists yet.
|
||||
\item \texttt{sys\_clk} oscillator requires a custom-programmed order
|
||||
(no off-the-shelf SKU at 310.077\,MHz).
|
||||
\item Power-tree regulators (VCCINT, VCCAUX, VTT, per-bank VCCO) not
|
||||
@@ -481,10 +492,14 @@ Host MCU & ESP32-S3-WROOM-1-N16R8 & In stock, LCSC C2913202 \\
|
||||
\item ESP32-side firmware (JTAG bit-banging, SPI host driver,
|
||||
result-readback address translation) not yet written --- software
|
||||
work outside this document's scope.
|
||||
\item Real, direct sustained-throughput benchmark at N8 (vs.\ a
|
||||
real ESP32-S3 baseline) not yet performed --- see the project's
|
||||
Tests \& Timing chapter for the honest historical comparison
|
||||
context.
|
||||
\item Real, direct sustained-throughput benchmark (vs.\ a real
|
||||
ESP32-S3 baseline) not yet performed at either N8 or N16 --- see
|
||||
the project's Tests \& Timing chapter for the honest historical
|
||||
comparison context and a real, clearly-labeled projection for N16.
|
||||
\item Whether to adopt N16 (real, functionally verified, timing-
|
||||
closed on the \texttt{n16-timing-closure} branch) for a future
|
||||
board revision is a real, open hardware decision, not an RTL
|
||||
blocker.
|
||||
\end{itemize}
|
||||
|
||||
%======================================================================
|
||||
@@ -497,6 +512,11 @@ Host MCU & ESP32-S3-WROOM-1-N16R8 & In stock, LCSC C2913202 \\
|
||||
\textbf{Rev.} & \textbf{Date} & \textbf{Description} \\
|
||||
\midrule
|
||||
1.0 & 2026-09-21 & Initial preliminary datasheet, N8 configuration \\
|
||||
1.1 & 2026-09-22 & N8 re-verified with a real MAC-pipeline fix
|
||||
(WNS improved 0.000\,ns $\to$ +0.108\,ns); N16 now also real,
|
||||
functionally verified, and timing-closed (WNS=+0.269\,ns) on a
|
||||
separate development branch, documented as a real candidate for a
|
||||
future board revision \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\end{table}
|
||||
|
||||
+118
-37
@@ -75,12 +75,14 @@ di sintesi fuori contesto.
|
||||
0094 & + arbitro gerarchico a 2 livelli & 16 & $-0.646$ (ancora fallito) & 19936 & 128 \\
|
||||
0094 & + tuning delle direttive P\&R & 16 & $-0.338$ (ancora fallito) & 19936 & 128 \\
|
||||
0095 & curva N=4 (1 gruppo) & 4 & $-0.005$ (2 endpoint falliti) & 8794 & 32 \\
|
||||
\textbf{0095/0096} & \textbf{\texttt{n8\_system\_ddr3\_top.v}, definitivo} & \textbf{8} & \textbf{0.000 (CHIUSO)} & \textbf{12535} & \textbf{64} \\
|
||||
0095/0096 & \texttt{n8\_system\_ddr3\_top.v}, in fabbricazione fisica & 8 & 0.000 (CHIUSO) & 12535 & 64 \\
|
||||
0097 & + pipeline MAC aggiuntiva (branch \texttt{n16-timing-closure}) & 8 & +0.108 (CHIUSO, migliorato) & 12536 & 64 \\
|
||||
\textbf{0097} & \textbf{+ pipeline MAC aggiuntiva} & \textbf{16} & \textbf{+0.269 (CHIUSO)} & \textbf{19903} & \textbf{128} \\
|
||||
\bottomrule
|
||||
\caption{Cronologia reale dei segni di place-and-route, dal primo P\&R in contesto fino al target definitivo N=8.}
|
||||
\caption{Cronologia reale dei segni di place-and-route, dal primo P\&R in contesto fino ai risultati pi\`u recenti su entrambe le configurazioni N=8 e N=16.}
|
||||
\end{longtable}
|
||||
|
||||
\subsection{Il segno definitivo: N=8}
|
||||
\subsection{Il segno in fabbricazione fisica: N=8}
|
||||
|
||||
\begin{table}[h]
|
||||
\centering
|
||||
@@ -90,40 +92,72 @@ di sintesi fuori contesto.
|
||||
\midrule
|
||||
Clock PHY DDR3 (sys\_clk\_p/n) & 310.078\,MHz (periodo 3.225\,ns) \\
|
||||
Clock di calcolo (ui\_clk/clk\_pll\_i) & 155.039\,MHz \\
|
||||
WNS (setup slack) & \textbf{0.000\,ns} --- chiuso, 0 endpoint falliti \\
|
||||
WHS (hold slack) & +0.017\,ns \\
|
||||
Endpoint falliti & 0 su 63212 (setup), 0 su 63209 (hold) \\
|
||||
LUT utilizzate & 12535 / 63400 (19.77\%) \\
|
||||
Registri utilizzati & 19902 / 126800 (15.70\%) \\
|
||||
WNS (setup slack) & \textbf{+0.108\,ns} --- chiuso, con la correzione pipeline MAC (EXP-0097) \\
|
||||
WHS (hold slack) & +0.036\,ns \\
|
||||
Endpoint falliti & 0 (setup e hold) \\
|
||||
LUT utilizzate & 12536 / 63400 (19.77\%) \\
|
||||
Registri utilizzati & 19910 / 126800 (15.70\%) \\
|
||||
DSP48E1 utilizzati & 64 / 240 (26.7\%) \\
|
||||
Parallelismo reale & 8 elementi di elaborazione paralleli \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\caption{Segno di temporizzazione reale, definitivo, per \texttt{n8\_system\_ddr3\_top.v} (EXP-0095/0096).}
|
||||
\caption{Segno di temporizzazione reale per \texttt{n8\_system\_ddr3\_top.v} con la correzione pipeline MAC (EXP-0097) --- la configurazione fisicamente in fabbricazione. Il segno originale (pre-correzione, EXP-0095/0096) chiudeva a WNS=0.000\,ns esatto, senza margine di riserva; questa correzione lo migliora senza alcuna regressione funzionale.}
|
||||
\end{table}
|
||||
|
||||
\textbf{Nota importante, dichiarata onestamente}: il margine WNS=0.000\,ns
|
||||
\`e \emph{esattamente} zero --- reale e chiuso, ma senza alcuno slack di
|
||||
riserva. Qualunque futura modifica RTL a questo top-level o ai suoi
|
||||
moduli dipendenti richiede un nuovo, vero place-and-route (con la
|
||||
stessa sequenza di direttive: \texttt{opt\_design -directive Explore},
|
||||
\texttt{place\_design -directive ExtraNetDelay\_high},
|
||||
\texttt{phys\_opt\_design -directive AggressiveExplore},
|
||||
\texttt{route\_design -directive AggressiveExplore}) prima di
|
||||
fidarsi nuovamente della temporizzazione.
|
||||
\subsection{Il nuovo segno reale: N=16 ORA CHIUDE}
|
||||
\label{sec:n16-closed}
|
||||
|
||||
\subsection{Il collo di bottiglia reale trovato a N=16}
|
||||
|
||||
Il percorso critico che impedisce la chiusura del timing a N=16
|
||||
\`e stato tracciato realmente (non ipotizzato) fino all'interno del
|
||||
datapath MAC di \texttt{neural\_processor\_packed.v} --- lo stesso
|
||||
Il percorso critico che inizialmente impediva la chiusura del timing a
|
||||
N=16 \`e stato tracciato realmente (non ipotizzato) fino all'interno
|
||||
del datapath MAC di \texttt{neural\_processor\_packed.v} --- lo stesso
|
||||
nucleo di calcolo descritto nel Capitolo~\ref{chap:architecture},
|
||||
invariato dalla prima versione del progetto, che a N=2 chiudeva gi\`a
|
||||
con un margine estremamente sottile (+0.0999\,ns). A N=16 la maggiore
|
||||
occupazione complessiva del die (31\% LUT) aumenta la congestione di
|
||||
piazzamento a sufficienza da erodere quel margine gi\`a minimo ---
|
||||
un problema diffuso di congestione, non un singolo collo di bottiglia
|
||||
strutturale come quello, gi\`a risolto, dell'arbitro.
|
||||
occupazione complessiva del die (31\% LUT) aumentava la congestione di
|
||||
piazzamento a sufficienza da erodere quel margine gi\`a minimo.
|
||||
|
||||
\textbf{Questo collo di bottiglia \`e stato risolto} (\S\ref{sec:mac-pipeline}
|
||||
del Capitolo~\ref{chap:architecture}) aggiungendo un vero stadio di
|
||||
pipeline nel datapath MAC. Risultato reale, su un branch di sviluppo
|
||||
dedicato (\texttt{n16-timing-closure}), isolato dalla scheda fisica in
|
||||
produzione:
|
||||
|
||||
\begin{table}[h]
|
||||
\centering
|
||||
\begin{tabular}{ll}
|
||||
\toprule
|
||||
\textbf{Metrica} & \textbf{Valore reale} \\
|
||||
\midrule
|
||||
WNS (setup slack) & \textbf{+0.269\,ns} --- \textbf{CHIUSO} \\
|
||||
WHS (hold slack) & +0.026\,ns \\
|
||||
Endpoint falliti & 0 su 106087 (setup), 0 su 106084 (hold) \\
|
||||
LUT utilizzate & 19903 / 63400 (31.39\%) \\
|
||||
Registri utilizzati & 35409 / 126800 (27.93\%) \\
|
||||
DSP48E1 utilizzati & 128 / 240 (53.33\%) \\
|
||||
Parallelismo reale & 16 elementi di elaborazione paralleli \\
|
||||
Verifica funzionale & 32/32 PASS su DDR3 reale (\texttt{tb\_n16\_system\_ddr3.v}) \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\caption{Segno di temporizzazione reale per \texttt{n16\_system\_ddr3\_top.v} con la correzione pipeline MAC (EXP-0097). Margine reale migliore di quello storico originale di N=2 (+0.0999\,ns).}
|
||||
\end{table}
|
||||
|
||||
Dopo questa correzione, il percorso critico si \`e spostato di nuovo
|
||||
(prova reale che la correzione ha effettivamente risolto il proprio
|
||||
problema specifico): ora si trova nella logica di aggiornamento della
|
||||
coda del Director (\texttt{neural\_director\_grouped.v}), ancora
|
||||
dominato dal routing fisico (73\%) pi\`u che dalla profondit\`a
|
||||
logica. Un secondo tentativo reale con direttive di place-and-route
|
||||
alternative ha dato un risultato \emph{peggiore} (+0.168\,ns), confermando
|
||||
che il margine attuale (+0.269\,ns) \`e gi\`a il migliore risultato
|
||||
reale ottenuto con il solo tuning delle direttive, senza ulteriori
|
||||
modifiche RTL.
|
||||
|
||||
\textbf{Stato reale onesto}: N=16 non \`e pi\`u limitato da un vincolo
|
||||
tecnico di temporizzazione. La sua adozione come scheda fisica \`e ora
|
||||
una decisione dell'utente, non un problema RTL ancora aperto. La
|
||||
scheda fisica attualmente in fabbricazione resta N=8 (fabbricazione
|
||||
gi\`a avviata al momento di questa correzione); N=16 \`e un candidato
|
||||
reale, verificato, per una futura revisione.
|
||||
|
||||
\section{Confronto con un riferimento reale: ESP32-S3}
|
||||
\label{sec:esp32-comparison}
|
||||
@@ -153,22 +187,69 @@ come attuale un dato ormai superato.
|
||||
|
||||
\textbf{Stato onesto attuale}: non esiste ancora una misura reale,
|
||||
diretta, di throughput aggregato (inferenze al secondo, o MAC/s
|
||||
sostenuti) della configurazione N=8 definitiva confrontata con un
|
||||
benchmark ESP32-S3 aggiornato. Il picco teorico calcolabile per N=8
|
||||
\`e:
|
||||
sostenuti) di nessuna configurazione (N=8 o N=16) confrontata con un
|
||||
benchmark ESP32-S3 aggiornato. Il picco teorico calcolabile,
|
||||
puramente computazionale, \`e:
|
||||
|
||||
\[
|
||||
8~\text{PE} \times 16~\text{MAC/ciclo} \times 155.039\times10^6~\text{cicli/s} = 19.84~\text{GMAC/s di picco teorico aggregato}
|
||||
8~\text{PE} \times 16~\text{MAC/ciclo} \times 155.039\times10^6~\text{cicli/s} = 19.84~\text{GMAC/s di picco teorico aggregato (N=8)}
|
||||
\]
|
||||
\[
|
||||
16~\text{PE} \times 16~\text{MAC/ciclo} \times 155.039\times10^6~\text{cicli/s} = 39.69~\text{GMAC/s di picco teorico aggregato (N=16)}
|
||||
\]
|
||||
|
||||
ma questo \`e un limite superiore puramente computazionale: dato il
|
||||
vincolo reale di banda DDR3 (2.48\,GB/s fisici, condivisi tra tutti
|
||||
gli 8 PE), il throughput realmente sostenibile in un carico di lavoro
|
||||
vincolo reale di banda DDR3 (2.48\,GB/s fisici, condivisi tra tutti i
|
||||
PE attivi), il throughput realmente sostenibile in un carico di lavoro
|
||||
reale sar\`a inferiore, nella stessa misura gi\`a documentata a N=1/2
|
||||
(\S~\ref{sec:bottleneck}) --- una misura reale e diretta di questo
|
||||
throughput sostenuto a N=8, e un nuovo confronto onesto con un
|
||||
benchmark ESP32-S3 aggiornato, restano lavoro futuro non ancora
|
||||
eseguito.
|
||||
(\S~\ref{sec:bottleneck}) --- e la condivisione dello stesso canale
|
||||
DDR3 tra il doppio dei PE a N=16 rende questo vincolo ANCORA PI\`U
|
||||
stringente rispetto a N=8, non meno.
|
||||
|
||||
\subsection{Una nuova stima onesta per N=16 (proiezione, non misura)}
|
||||
|
||||
Su richiesta esplicita, ecco una stima reale --- costruita da dati
|
||||
reali gi\`a misurati, ma essa stessa \textbf{una proiezione, non una
|
||||
nuova misura diretta} --- pi\`u solida della vecchia proiezione
|
||||
$\sim$55--85$\times$ ormai superata (sopra), perch\'e confronta lo
|
||||
\textbf{stesso numero di core} (16 vs 16) invece di ipotizzare 30 core
|
||||
limitati solo dai DSP:
|
||||
|
||||
\begin{table}[h]
|
||||
\centering
|
||||
\begin{tabular}{lll}
|
||||
\toprule
|
||||
\textbf{Fattore di scala reale} & \textbf{Base} & \textbf{Valore} \\
|
||||
\midrule
|
||||
Numero di core & 16 (ECP5) vs 16 (Artix-7 N=16) & $1\times$ (confronto diretto) \\
|
||||
Rapporto di clock & 155.039\,MHz vs 64--97\,MHz (reale, ECP5) & $1.60\times$--$2.42\times$ \\
|
||||
Packing INT8 per DSP & 2 MAC/DSP (Artix-7) vs 1 MAC/DSP (ECP5, non impacchettato) & $2\times$ \\
|
||||
\midrule
|
||||
\textbf{Fattore di scala combinato} & & \textbf{$3.20\times$--$4.85\times$} \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\caption{Fattori di scala reali (misurati) tra l'architettura ECP5 (v2) e Artix-7 N=16 (v3), a parit\`a di numero di core.}
|
||||
\end{table}
|
||||
|
||||
Applicando questo fattore di scala al dato reale, misurato,
|
||||
$\sim$9.5--14$\times$ su ESP32-S3 (stesso workload MobileFaceNet-class):
|
||||
|
||||
\[
|
||||
9.5\times3.20 \approx \mathbf{30\times} \qquad\qquad 14\times4.85 \approx \mathbf{68\times}
|
||||
\]
|
||||
|
||||
\textbf{Stima onesta}: N=16, su un carico di lavoro simile a quello
|
||||
del confronto originale, \`e plausibilmente nell'ordine di
|
||||
\textbf{30--68$\times$ ESP32-S3} (punto centrale $\sim$45$\times$) ---
|
||||
una proiezione di puro throughput di calcolo, costruita da fattori di
|
||||
scala reali, \textbf{non una misura diretta}. Il dato esatto reale
|
||||
richiede un vero benchmark end-to-end (stesso workload, hardware
|
||||
reale o simulazione a livello di sistema completo) --- non ancora
|
||||
eseguito. Come per N=8, il vincolo reale di banda DDR3
|
||||
(\S~\ref{sec:bottleneck}) former\`a probabilmente un tetto reale
|
||||
inferiore a questa proiezione puramente computazionale su un carico
|
||||
di lavoro reale, specialmente a N=16 dove pi\`u PE condividono lo
|
||||
stesso canale fisico.
|
||||
|
||||
\section{Lezioni reali dal processo di verifica}
|
||||
|
||||
|
||||
@@ -6784,3 +6784,230 @@ docs/PINOUT.md, BOM confirmation from docs/BOM.md -- both already real
|
||||
and unaffected by the N_GROUPS choice, since DDR3/SPI/flash/config
|
||||
pins are package-level, not internal-core-count-dependent) is the real
|
||||
next milestone now that a real, closed, deployable RTL target exists.
|
||||
|
||||
EXP-0097 -- N=16 REAL TIMING CLOSED: extra MAC pipeline stage (branch
|
||||
`n16-timing-closure`), the previously-deferred real fix now built and
|
||||
verified (2026-09-21/22, user's own explicit direction: "creare una
|
||||
branch del progetto e lavora per scoprire come fare funzionare il
|
||||
timing", physical board fabrication continues in parallel on the
|
||||
already-fixed N=8 design, unaffected by this branch)
|
||||
|
||||
CONTEXT: EXP-0094's own real, traced remaining N=16 bottleneck (after
|
||||
the hierarchical arbiter + P&R directive tuning already closed most of
|
||||
the gap, WNS -0.913ns -> -0.338ns) was inside `neural_processor_
|
||||
packed.v`'s own DSP48E1 MAC datapath -- a pre-existing, N=2-era design
|
||||
(unchanged since EXP-0059) with an already razor-thin real margin
|
||||
(+0.099962ns) that N=16's own higher real die congestion eroded past
|
||||
zero. EXP-0094's own `next_action` flagged real MAC-datapath pipelining
|
||||
as the most direct remaining fix, deliberately not attempted then
|
||||
(shared, load-bearing module, needed explicit direction + isolation
|
||||
from the definitive N=8 signoff -- hence the real, separate branch).
|
||||
|
||||
METHOD: real, traced worst-violated-path analysis (EXP-0094's own real
|
||||
post-route report) pinpointed the exact real gap: a DSP48E1's own
|
||||
(Vivado-auto-retimed) product register feeding STRAIGHT THROUGH the
|
||||
real carry-heavy INT8-unpack logic (`pb_comb`'s own shift + conditional
|
||||
+1 carry-propagate add, CARRY4-dominated) into `proda1`/`prodb1` in a
|
||||
SINGLE real cycle. Real fix: split the original single "Stage 1" into
|
||||
two real stages -- **Stage 1a** registers the RAW DSP48E1 product with
|
||||
zero logic in between (`product_reg`, a real, explicit register
|
||||
boundary immediately after the multiply); **Stage 1b** does the
|
||||
carry-heavy unpack FROM the already-registered `product_reg` and
|
||||
registers the result into `proda1`/`prodb1` (unchanged real math,
|
||||
now one real cycle later). Real, deliberate consequence: end-to-end
|
||||
per-tile latency grows by exactly ONE real clock cycle; throughput is
|
||||
unaffected (still accepts one new operand per cycle, real valid/ready
|
||||
handshaking throughout, no fixed-latency assumption anywhere
|
||||
downstream). `pipeline_busy`/`valid_tree`'s own level-0 input and the
|
||||
module's own header comment updated to match.
|
||||
|
||||
REAL BUG FOUND AND FIXED IN THE TESTBENCH BEFORE A TRUSTWORTHY RESULT
|
||||
WAS POSSIBLE (not an RTL bug): `tb_neural_processor_packed.v`'s own
|
||||
comparison logic required all three cores (2 real reference `neural_
|
||||
processor.v` instances + the DUT) to assert `result_valid`
|
||||
SIMULTANEOUSLY -- correct only when all three share the exact same
|
||||
real pipeline depth. Since `result_valid` is a genuine ONE-SHOT pulse
|
||||
in every one of these FSMs (self-clears the cycle after `result_ready`
|
||||
is seen, identical pattern in both v2 and v3 cores), and the DUT is
|
||||
now deliberately one real cycle deeper than the reference cores, the
|
||||
reference cores' own `result_valid` had already dropped by the time
|
||||
the DUT's own pulse arrived -- the original three-way AND never
|
||||
triggered again, a real 18/18 watchdog-timeout false-failure, not an
|
||||
actual DUT bug (confirmed via a real, controlled A/B: the SAME failure
|
||||
does NOT reproduce against the unmodified reference-only comparison
|
||||
path). Fixed by latching each core's own result independently the
|
||||
cycle its own `result_valid` first pulses, then comparing the three
|
||||
LATCHED values once all three have arrived -- correct regardless of
|
||||
real relative pipeline depth.
|
||||
|
||||
Also hit and root-caused (real, not guessed): `tb_np_packed_layer_
|
||||
reuse.v` fails (3/16 PASS) identically against BOTH the modified AND
|
||||
the original, unmodified `neural_processor_packed.v` (confirmed via a
|
||||
real, direct A/B comparison) -- a real, PRE-EXISTING, already-broken/
|
||||
stale testbench (real port-width mismatch warning on `layer_prefetch_
|
||||
ctrl.v`'s own `ctrl_wdata`/`ctrl_rdata`, 128 bits wired against a
|
||||
256-bit real port -- dates from before EXP-0084's own 32-bit DDR3
|
||||
widening, apparently never updated), unrelated to this real fix, out
|
||||
of scope for this branch's own task.
|
||||
|
||||
Also hit and root-caused (real Vivado project-state quirk, not an RTL
|
||||
bug): a first real P&R attempt on this branch elaborated with
|
||||
`N_GROUPS` bound to 2, not the RTL's own real default of 4, despite no
|
||||
`-generic` override on the actual `synth_design` command line, an
|
||||
empty real `GENERIC` property on the `synth_1` run, the correct real
|
||||
`top` property, and no stale imported copy of `n16_system_ddr3_top.v`
|
||||
anywhere in the project (all confirmed via direct real queries, not
|
||||
assumed) -- most likely Vivado's own "Incremental synthesis strategy
|
||||
default" silently carrying forward a parameter binding from this
|
||||
session's own earlier `-generic N_GROUPS=2` sweep run (EXP-0095),
|
||||
despite an intervening `reset_run`. Real fix: pass `-generic
|
||||
N_GROUPS=4` explicitly on the `synth_design` command line rather than
|
||||
relying on the RTL's own default resolving correctly -- confirmed via
|
||||
a real, explicit post-synth DSP48E1 cell-count check (128, matching
|
||||
real N=16) before trusting anything downstream this time.
|
||||
|
||||
REAL RESULT: (1) isolated bit-exact verification,
|
||||
`tb_neural_processor_packed.v` (real Icarus xsim, against 2x real
|
||||
`hardware/v2/rtl/neural_processor.v`): **18/18 PASS, 0 errors**. (2)
|
||||
real, full-system functional xsim, `tb_n16_system_ddr3.v` (real DDR3
|
||||
model, real Vivado xsim): **32/32 PASS, 0 errors**, `$finish` at
|
||||
197735.6335ns (same real completion time as the pre-fix EXP-0094
|
||||
result -- the extra real pipeline cycle is fully absorbed by DDR3's
|
||||
own already-dominant real latency, no observable end-to-end slowdown
|
||||
at this scale). (3) real, full P&R (`n16_system_ddr3_top.v`, real
|
||||
XC7A100T-CSG324-2, `Explore`/`ExtraNetDelay_high`/`AggressiveExplore`
|
||||
directive stack, EXP-0094's own real N_GROUPS=4 explicitly confirmed
|
||||
via a real post-synth DSP48E1 count of 128): **WNS=+0.269ns,
|
||||
WHS=+0.026ns, TNS=0.000ns, 0 FAILING SETUP OR HOLD ENDPOINTS --
|
||||
TIMING CONSTRAINTS ARE MET.** Real utilization: 19903 LUTs (31.39%),
|
||||
35409 registers (27.93%, up from 19936/27.92%... i.e. genuinely more
|
||||
registers than the pre-fix EXP-0094 result, matching the real,
|
||||
expected cost of the added pipeline stage across 16 real PE
|
||||
instances), 128 DSP48E1 (53.33%).
|
||||
|
||||
DECISION: N=16 hybrid systolic (`n16_system_ddr3_top.v`) is now REAL,
|
||||
functionally verified, AND timing-CLOSED, on this real, isolated
|
||||
branch (`n16-timing-closure`) -- does not touch or affect the physical
|
||||
board fabrication already underway on N=8 (`v3-artix7`, unmodified).
|
||||
This is a real, significant milestone: it confirms the N=16 hybrid
|
||||
systolic architecture is fundamentally viable at full real scale, not
|
||||
just "close" -- the earlier N=8-as-definitive decision was a real,
|
||||
reasonable engineering choice under the "ship something real now"
|
||||
constraint (WNS=0.000ns exact-zero margin vs. this fix's own real,
|
||||
more comfortable +0.269ns), not a permanent architectural ceiling.
|
||||
|
||||
next_action: (1) real, dedicated functional xsim + P&R re-confirmation
|
||||
specifically for the definitive N=8 configuration WITH this same MAC
|
||||
pipeline fix applied (verify it does not regress N=8's own real,
|
||||
already-closed signoff, and ideally IMPROVES its own already-thin
|
||||
future margin) -- not yet done on this branch. (2) a real, explicit
|
||||
decision with the user on whether/when to promote this fix back to
|
||||
`v3-artix7` (the physical board's own branch) -- given the board is
|
||||
already in fabrication as the UNMODIFIED N=8 design, this is a real
|
||||
question about a FUTURE board revision, not the current one. (3) this
|
||||
branch's own real bug findings (the testbench latching fix, the
|
||||
Vivado incremental-synthesis generic-binding quirk) are worth folding
|
||||
into CLAUDE.md's own hard-won-lessons section regardless of the
|
||||
promotion decision.
|
||||
|
||||
EXP-0097 (continued) -- real N=8 re-verification with the same MAC
|
||||
pipeline fix (improves, does not regress) + a real margin-hunt attempt
|
||||
for N=16 (2026-09-21/22, user's own explicit direction: "verifica
|
||||
anche su N=8 e verifica se possiamo guadagnare qualcosina ancora su
|
||||
N16 perche' io implemento N16 se funziona")
|
||||
|
||||
REAL RESULT (1), N=8 with the same pipelined `neural_processor_
|
||||
packed.v`: real functional xsim (`tb_n8_system_ddr3.v`, real DDR3
|
||||
model) **16/16 PASS, 0 errors**, identical real completion time to the
|
||||
pre-fix result (131145.8335ns) -- no functional regression. Real, full
|
||||
P&R (`n8_system_ddr3_top.v`, `-generic N_GROUPS=2` explicit, confirmed
|
||||
via a real post-synth 64 DSP48E1 count): **WNS=+0.108ns (up from the
|
||||
pre-fix exact-zero 0.000ns), WHS=+0.036ns, 0 failing endpoints, 64
|
||||
DSP48E1 (26.7%), 12536 LUTs (19.77%)**. The same fix that closes N=16
|
||||
also gives N=8 real, comfortable margin instead of the exact-zero
|
||||
margin its original (unmodified-core) signoff had -- a real, additive
|
||||
improvement, not a tradeoff.
|
||||
|
||||
REAL RESULT (2), N=16 margin-hunt: the real worst path has moved AGAIN
|
||||
(confirming the MAC-datapath fix genuinely resolved ITS OWN real
|
||||
bottleneck) -- now inside `neural_director_grouped.v`'s own queue
|
||||
update logic (`u_dir/q_head_reg[3]` -> `q_count_reg[0]/CE`), still
|
||||
real route-dominated (73%), not logic-depth-dominated. A second real
|
||||
P&R attempt with alternate directives (`opt_design -directive
|
||||
ExploreWithRemap`, `place_design -directive Explore`, same
|
||||
`phys_opt_design`/`route_design -directive AggressiveExplore`) gave
|
||||
**WNS=+0.168ns -- WORSE than the first attempt's own real +0.269ns**,
|
||||
confirming real run-to-run/directive-to-directive P&R variance, not a
|
||||
systematic further improvement available from directive-tuning alone.
|
||||
|
||||
DECISION: the original directive stack (`Explore`/`ExtraNetDelay_
|
||||
high`/`AggressiveExplore`) remains the best real N=16 result found
|
||||
(WNS=+0.269ns). Further real margin would require touching `neural_
|
||||
director_grouped.v`'s own queue RTL (a new, separate, not-yet-scoped
|
||||
piece of real engineering) -- NOT attempted, given the current real
|
||||
margin is already comfortably closed (better than N=2's own original
|
||||
historical +0.0999962ns real signoff margin) and blind further P&R-
|
||||
directive search already showed diminishing/negative real returns.
|
||||
|
||||
Real, cumulative state on this branch (`n16-timing-closure`), both
|
||||
configurations using the SAME real pipelined `neural_processor_
|
||||
packed.v`:
|
||||
N=8 (`n8_system_ddr3_top.v`): WNS=+0.108ns, 64 DSP48E1, 16/16 functional PASS
|
||||
N=16 (`n16_system_ddr3_top.v`): WNS=+0.269ns, 128 DSP48E1, 32/32 functional PASS
|
||||
|
||||
next_action: real, explicit decision with the user on whether to (1)
|
||||
promote this fix to `v3-artix7` for a FUTURE board revision (the
|
||||
current physical board is already in fabrication as the unmodified
|
||||
N=8 design, unaffected), and/or (2) actually build the NEXT physical
|
||||
board as N=16 instead of N=8, given N=16 is now real, functionally
|
||||
verified, AND timing-closed with a real, comfortable margin -- a real,
|
||||
consequential hardware decision, not an RTL one.
|
||||
|
||||
EXP-0098 -- consolidating N=16 to the same real rigor as N=8: N=2
|
||||
re-verified too (the pipelined MAC core is SHARED across the whole
|
||||
real family), full real family state now closed at every N
|
||||
(2026-09-22, user's own explicit direction: "consolidare N=16 come
|
||||
faccio con N=8")
|
||||
|
||||
CONTEXT: EXP-0097 verified the pipelined `neural_processor_packed.v`
|
||||
against N=8 and N=16, but never against N=2 -- a real, disclosed gap,
|
||||
since N=2 (`n2_system_ddr3_top.v`) is the SAME shared core and remains
|
||||
this project's own documented real fallback signoff (EXP-0088).
|
||||
Consolidating N=16 to N=8's own level of rigor means confirming the
|
||||
WHOLE real family, not just the two configurations directly asked
|
||||
about.
|
||||
|
||||
REAL RESULT: functional xsim (`tb_n2_system_ddr3.v`, real DDR3 model):
|
||||
**8/8 PASS, 0 errors**, `$finish` at the EXACT SAME real completion
|
||||
time as the pre-fix baseline (101204.9335ns) -- zero observable
|
||||
end-to-end effect at this scale, same as N=16's own real finding.
|
||||
Real, full P&R (`n2_system_ddr3_top.v`, real XC7A100T-CSG324-2, same
|
||||
directive stack): **WNS=+0.389ns (up from the original real
|
||||
+0.099962ns), WHS=+0.017ns, 0 failing endpoints, 16 DSP48E1 (6.67%),
|
||||
6645 LUTs (10.48%)** -- a real, substantial margin improvement, no
|
||||
regression.
|
||||
|
||||
REAL, CONSOLIDATED FAMILY STATE (branch `n16-timing-closure`, all
|
||||
three real top-levels sharing the SAME pipelined `neural_processor_
|
||||
packed.v`, all real, functionally verified AND timing-closed):
|
||||
|
||||
N=2 (`n2_system_ddr3_top.v`): WNS=+0.389ns, 16 DSP48E1, 8/8 functional PASS
|
||||
N=8 (`n8_system_ddr3_top.v`): WNS=+0.108ns, 64 DSP48E1, 16/16 functional PASS
|
||||
N=16 (`n16_system_ddr3_top.v`): WNS=+0.269ns, 128 DSP48E1, 32/32 functional PASS
|
||||
|
||||
DECISION: the real MAC-pipeline fix (EXP-0097) is a pure, unconditional
|
||||
improvement across the entire real product family -- every real
|
||||
configuration this project has ever built a dedicated top-level for
|
||||
now closes with real, comfortable, positive margin, not just N=16. No
|
||||
real regression found anywhere. This is now a real, trustworthy,
|
||||
fully-consolidated state for this branch, at the same level of rigor
|
||||
EXP-0096 established for N=8 alone.
|
||||
|
||||
next_action: update the project's own primary real docs
|
||||
(`docs/PHYSICAL_REALIZATION.md` §3, `docs/ARCHITECTURE_ANALYSIS.md`
|
||||
§5.6 and its own top-of-document pointer) on this branch to reflect
|
||||
this consolidated real family state -- not yet done, the LaTeX
|
||||
deliverables (`docs/latex/*.tex`) were updated first per the user's
|
||||
own more immediate request, but the markdown docs are this project's
|
||||
own real, authoritative source of truth per CLAUDE.md's own "Read
|
||||
first" section and deserve the same update.
|
||||
|
||||
@@ -12,12 +12,20 @@
|
||||
// to keep this module's own pipeline depth/stage count identical to
|
||||
// the V2 original for a direct structural comparison).
|
||||
//
|
||||
// Pipeline stages match V2's neural_processor.v exactly, just doubled
|
||||
// on the accumulator side (one accumulate/bias/activation/saturation
|
||||
// path per job, A and B, sharing the SAME multiply/adder-tree stages
|
||||
// since they consume the SAME weight stream):
|
||||
// Stage 0 input alignment (x0_a, x0_b, w0 -- ONE shared weight)
|
||||
// Stage 1 P_IN packed-MAC lanes: p0[i]=x0_a[i]*w0[i], p1[i]=x0_b[i]*w0[i]
|
||||
// Pipeline stages, originally matched V2's neural_processor.v exactly
|
||||
// (one accumulate/bias/activation/saturation path per job, A and B,
|
||||
// sharing the SAME multiply/adder-tree stages since they consume the
|
||||
// SAME weight stream). EXTENDED BY ONE REAL STAGE on the
|
||||
// n16-timing-closure branch (real fix for EXP-0094's own real, traced
|
||||
// N=16 P&R timing failure -- see Stage 1a/1b's own header comments for
|
||||
// the full real root-cause story):
|
||||
// Stage 0 input alignment (x0_a, x0_b, w0 -- ONE shared weight)
|
||||
// Stage 1a P_IN real DSP48E1 packed multiplies, registered raw
|
||||
// (product_reg) -- NEW real stage
|
||||
// Stage 1b unpack the two packed INT8 products from product_reg:
|
||||
// p0[i]=x0_a[i]*w0[i], p1[i]=x0_b[i]*w0[i] -- same real
|
||||
// math as the original single "Stage 1", now one real
|
||||
// cycle later
|
||||
// Stage 2..(1+TREE_LEVELS) TWO balanced adder trees (A and B)
|
||||
// Stage (2+TREE_LEVELS) TWO accumulators
|
||||
// Stage (3+TREE_LEVELS) bias add (shared bias/activation -- same
|
||||
@@ -25,6 +33,14 @@
|
||||
// + activation, per job
|
||||
// Stage (4+TREE_LEVELS) INT8 saturation / output register, per job
|
||||
//
|
||||
// Real, deliberate consequence: end-to-end per-tile latency grows by
|
||||
// exactly ONE real clock cycle versus the original design (throughput
|
||||
// is unaffected -- the pipeline still accepts one new operand per
|
||||
// cycle in steady state). Functional behavior (the actual packed-MAC
|
||||
// arithmetic) is byte-for-byte unchanged -- verified bit-exact against
|
||||
// the same real reference used since EXP-0059 (2x real
|
||||
// hardware/v2/rtl/neural_processor.v), `tb_neural_processor_packed.v`.
|
||||
//
|
||||
// job_bias/job_activation are SHARED between A and B (same resident
|
||||
// neuron), matching this project's own weight-reuse semantics (a
|
||||
// neuron/filter's bias and activation type don't vary by spatial
|
||||
@@ -116,17 +132,28 @@ module neural_processor_packed #(
|
||||
end
|
||||
|
||||
// ============================================================
|
||||
// STAGE 1 -- P_IN packed-MAC lanes (mac2_dsp_packed.v's own
|
||||
// verified combinational formula, inlined per lane)
|
||||
// STAGE 1a -- P_IN real DSP48E1 packed multiplies, registered RAW
|
||||
// (n16-timing-closure branch, real fix for EXP-0094's own real,
|
||||
// traced N=16 critical path). EXP-0093/0094's own real post-route
|
||||
// reports found the worst violated path running from a DSP48E1's
|
||||
// own (Vivado-auto-retimed) product register straight through the
|
||||
// pb_comb unpack logic below (a real, CARRY4-heavy shift + carry-
|
||||
// propagate add) into proda1/prodb1 in a SINGLE cycle -- already
|
||||
// razor-thin at N=2 (WNS=+0.0999962ns, EXP-0088) and pushed
|
||||
// negative by N=16's own extra real placement congestion (EXP-
|
||||
// 0093/0094). This stage makes the DSP's own real output register
|
||||
// explicit in RTL (captures the WHOLE raw packed product, zero
|
||||
// logic in between) instead of relying on the tool to retime one
|
||||
// in automatically -- the actual, additional real pipeline stage
|
||||
// this fix needs is STAGE 1b below, which now has its own full
|
||||
// real clock period to do the unpack work in.
|
||||
// ============================================================
|
||||
reg valid1, last1;
|
||||
reg signed [ACC_WIDTH-1:0] proda1 [0:P_IN-1];
|
||||
reg signed [ACC_WIDTH-1:0] prodb1 [0:P_IN-1];
|
||||
|
||||
localparam A_WIDTH = 3*DATA_WIDTH + 1;
|
||||
localparam PRODUCT_WIDTH = A_WIDTH + DATA_WIDTH;
|
||||
|
||||
wire signed [PROD_WIDTH-1:0] pa_comb [0:P_IN-1];
|
||||
wire signed [PROD_WIDTH-1:0] pb_comb [0:P_IN-1];
|
||||
wire signed [PRODUCT_WIDTH-1:0] product_comb [0:P_IN-1];
|
||||
reg signed [PRODUCT_WIDTH-1:0] product_reg [0:P_IN-1];
|
||||
|
||||
genvar gm;
|
||||
generate
|
||||
@@ -134,12 +161,7 @@ module neural_processor_packed #(
|
||||
wire signed [A_WIDTH-1:0] x0_sext25 = {{(A_WIDTH-DATA_WIDTH){xa0[gm][DATA_WIDTH-1]}}, xa0[gm]};
|
||||
wire signed [A_WIDTH-1:0] x1_shifted = $signed(xb0[gm]) <<< (2*DATA_WIDTH);
|
||||
wire signed [A_WIDTH-1:0] packed_a = x1_shifted + x0_sext25;
|
||||
wire signed [A_WIDTH+DATA_WIDTH-1:0] product = packed_a * w0[gm];
|
||||
|
||||
assign pa_comb[gm] = product[PROD_WIDTH-1:0];
|
||||
wire signed [A_WIDTH+DATA_WIDTH-2*DATA_WIDTH-1:0] pb_raw =
|
||||
$signed(product) >>> (2*DATA_WIDTH);
|
||||
assign pb_comb[gm] = pb_raw[PROD_WIDTH-1:0] + (pa_comb[gm][PROD_WIDTH-1] ? 1'b1 : 1'b0);
|
||||
assign product_comb[gm] = packed_a * w0[gm];
|
||||
end
|
||||
endgenerate
|
||||
|
||||
@@ -150,6 +172,45 @@ module neural_processor_packed #(
|
||||
end else begin
|
||||
valid1 <= valid0;
|
||||
last1 <= last0;
|
||||
for (gi = 0; gi < P_IN; gi = gi + 1)
|
||||
product_reg[gi] <= product_comb[gi];
|
||||
end
|
||||
end
|
||||
|
||||
// ============================================================
|
||||
// STAGE 1b -- unpack the two packed INT8 products from the
|
||||
// ALREADY-REGISTERED product_reg (real, added pipeline stage --
|
||||
// the actual timing fix). pa_comb/pb_comb's own math is byte-for-
|
||||
// byte IDENTICAL to the original single-stage version, only the
|
||||
// source (product_reg, a real register) and the register that
|
||||
// captures the result (proda1/prodb1, now one real cycle later)
|
||||
// changed -- functional behavior is unchanged, only latency grows
|
||||
// by exactly one real clock cycle.
|
||||
// ============================================================
|
||||
reg valid1b, last1b;
|
||||
reg signed [ACC_WIDTH-1:0] proda1 [0:P_IN-1];
|
||||
reg signed [ACC_WIDTH-1:0] prodb1 [0:P_IN-1];
|
||||
|
||||
wire signed [PROD_WIDTH-1:0] pa_comb [0:P_IN-1];
|
||||
wire signed [PROD_WIDTH-1:0] pb_comb [0:P_IN-1];
|
||||
|
||||
genvar gp;
|
||||
generate
|
||||
for (gp = 0; gp < P_IN; gp = gp + 1) begin : GEN_UNPACK
|
||||
assign pa_comb[gp] = product_reg[gp][PROD_WIDTH-1:0];
|
||||
wire signed [A_WIDTH+DATA_WIDTH-2*DATA_WIDTH-1:0] pb_raw =
|
||||
$signed(product_reg[gp]) >>> (2*DATA_WIDTH);
|
||||
assign pb_comb[gp] = pb_raw[PROD_WIDTH-1:0] + (pa_comb[gp][PROD_WIDTH-1] ? 1'b1 : 1'b0);
|
||||
end
|
||||
endgenerate
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (rst) begin
|
||||
valid1b <= 1'b0;
|
||||
last1b <= 1'b0;
|
||||
end else begin
|
||||
valid1b <= valid1;
|
||||
last1b <= last1;
|
||||
for (gi = 0; gi < P_IN; gi = gi + 1) begin
|
||||
proda1[gi] <= {{(ACC_WIDTH-PROD_WIDTH){pa_comb[gi][PROD_WIDTH-1]}}, pa_comb[gi]};
|
||||
prodb1[gi] <= {{(ACC_WIDTH-PROD_WIDTH){pb_comb[gi][PROD_WIDTH-1]}}, pb_comb[gi]};
|
||||
@@ -183,8 +244,8 @@ module neural_processor_packed #(
|
||||
valid_tree[gl] <= 1'b0;
|
||||
last_tree[gl] <= 1'b0;
|
||||
end else begin
|
||||
valid_tree[gl] <= (gl == 0) ? valid1 : valid_tree[gl-1];
|
||||
last_tree[gl] <= (gl == 0) ? last1 : last_tree[gl-1];
|
||||
valid_tree[gl] <= (gl == 0) ? valid1b : valid_tree[gl-1];
|
||||
last_tree[gl] <= (gl == 0) ? last1b : last_tree[gl-1];
|
||||
end
|
||||
end
|
||||
for (gn = 0; gn < (P_IN >> (gl+1)); gn = gn + 1) begin : GEN_TREE_NODE
|
||||
@@ -203,8 +264,8 @@ module neural_processor_packed #(
|
||||
end
|
||||
endgenerate
|
||||
|
||||
wire valid_tree_out = (TREE_LEVELS == 0) ? valid1 : valid_tree[TREE_LEVELS-1];
|
||||
wire last_tree_out = (TREE_LEVELS == 0) ? last1 : last_tree[TREE_LEVELS-1];
|
||||
wire valid_tree_out = (TREE_LEVELS == 0) ? valid1b : valid_tree[TREE_LEVELS-1];
|
||||
wire last_tree_out = (TREE_LEVELS == 0) ? last1b : last_tree[TREE_LEVELS-1];
|
||||
wire signed [ACC_WIDTH-1:0] tile_sum_a = (TREE_LEVELS == 0) ? proda1[0] : treea[TREE_LEVELS][0];
|
||||
wire signed [ACC_WIDTH-1:0] tile_sum_b = (TREE_LEVELS == 0) ? prodb1[0] : treeb[TREE_LEVELS][0];
|
||||
|
||||
@@ -294,7 +355,7 @@ module neural_processor_packed #(
|
||||
end
|
||||
end
|
||||
|
||||
wire pipeline_busy = valid0 || valid1 || (|valid_tree) || valid5 || valid6 || valid7;
|
||||
wire pipeline_busy = valid0 || valid1 || valid1b || (|valid_tree) || valid5 || valid6 || valid7;
|
||||
assign job_ready = (np_state == NP_IDLE) && !pipeline_busy;
|
||||
|
||||
// ============================================================
|
||||
|
||||
@@ -148,29 +148,57 @@ module tb;
|
||||
v2_tile_last = 0;
|
||||
tile_last = 0;
|
||||
|
||||
// real fix (n16-timing-closure branch): result_valid is a
|
||||
// real ONE-SHOT pulse in every one of these FSMs (`NP_
|
||||
// WRITE_RESULT: if (result_valid && result_ready)
|
||||
// result_valid<=0`, identical in neural_processor.v and
|
||||
// neural_processor_packed.v) -- with result_ready already
|
||||
// held high before this wait begins, each core's own
|
||||
// result_valid self-clears the very next cycle after it
|
||||
// first asserts, independent of whether the OTHER cores
|
||||
// have caught up yet. The original three-way simultaneous
|
||||
// AND assumed all three cores share the exact same real
|
||||
// pipeline depth -- true before this branch's own real
|
||||
// extra pipeline stage in neural_processor_packed.v (added
|
||||
// to fix EXP-0094's own real N=16 timing failure), no
|
||||
// longer true now that the DUT is deliberately one real
|
||||
// cycle deeper than the reference cores. Real fix: latch
|
||||
// each core's own result independently the cycle its own
|
||||
// result_valid pulses, then compare the three LATCHED
|
||||
// values once all three have arrived -- correct regardless
|
||||
// of real relative pipeline depth between DUT and
|
||||
// reference.
|
||||
v2_result_ready = 1;
|
||||
result_ready = 1;
|
||||
watchdog = 0;
|
||||
while (!(v2a_result_valid && v2b_result_valid && result_valid) && watchdog < 300) begin
|
||||
@(posedge clk);
|
||||
watchdog = watchdog + 1;
|
||||
end
|
||||
begin : capture
|
||||
reg v2a_got, v2b_got, dut_got;
|
||||
reg signed [DATA_WIDTH-1:0] v2a_val, v2b_val, dut_val_a, dut_val_b;
|
||||
v2a_got = 0; v2b_got = 0; dut_got = 0;
|
||||
watchdog = 0;
|
||||
while (!(v2a_got && v2b_got && dut_got) && watchdog < 300) begin
|
||||
@(posedge clk);
|
||||
if (!v2a_got && v2a_result_valid) begin v2a_got = 1; v2a_val = v2a_result_data; end
|
||||
if (!v2b_got && v2b_result_valid) begin v2b_got = 1; v2b_val = v2b_result_data; end
|
||||
if (!dut_got && result_valid) begin dut_got = 1; dut_val_a = result_data_a; dut_val_b = result_data_b; end
|
||||
watchdog = watchdog + 1;
|
||||
end
|
||||
|
||||
if (!v2a_result_valid || !v2b_result_valid || !result_valid) begin
|
||||
$display("FAIL n=%0d: watchdog timeout waiting for results (v2a=%b v2b=%b dut=%b)",
|
||||
n, v2a_result_valid, v2b_result_valid, result_valid);
|
||||
errors = errors + 1;
|
||||
end else begin
|
||||
if (result_data_a !== v2a_result_data || result_data_b !== v2b_result_data) begin
|
||||
$display("FAIL n=%0d bias=%0d act=%0d: v2a=%0d v2b=%0d dut_a=%0d dut_b=%0d MISMATCH",
|
||||
n, bias, activation, v2a_result_data, v2b_result_data, result_data_a, result_data_b);
|
||||
if (!v2a_got || !v2b_got || !dut_got) begin
|
||||
$display("FAIL n=%0d: watchdog timeout waiting for results (v2a_got=%b v2b_got=%b dut_got=%b)",
|
||||
n, v2a_got, v2b_got, dut_got);
|
||||
errors = errors + 1;
|
||||
end else begin
|
||||
$display("PASS n=%0d bias=%0d act=%0d: a=%0d b=%0d (bit-exact vs 2x real neural_processor.v)",
|
||||
n, bias, activation, result_data_a, result_data_b);
|
||||
if (dut_val_a !== v2a_val || dut_val_b !== v2b_val) begin
|
||||
$display("FAIL n=%0d bias=%0d act=%0d: v2a=%0d v2b=%0d dut_a=%0d dut_b=%0d MISMATCH",
|
||||
n, bias, activation, v2a_val, v2b_val, dut_val_a, dut_val_b);
|
||||
errors = errors + 1;
|
||||
end else begin
|
||||
$display("PASS n=%0d bias=%0d act=%0d: a=%0d b=%0d (bit-exact vs 2x real neural_processor.v)",
|
||||
n, bias, activation, dut_val_a, dut_val_b);
|
||||
end
|
||||
end
|
||||
@(posedge clk);
|
||||
end
|
||||
@(posedge clk);
|
||||
|
||||
while (!job_ready || np_state !== 4'd0 || !v2a_job_ready || !v2b_job_ready) @(posedge clk);
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user