6 Commits
Author SHA1 Message Date
micheleandClaude Sonnet 5 785e9461a0 docs: ESP32 host firmware specification (docs/FIRMWARE_SPEC.md)
Real, from-source spec for the ESP32-S3 host firmware -- every field
layout, address formula, and timing number is read directly from the
committed RTL (spi_host_bridge_v3.v, neural_director_grouped.v,
result_writeback.v, host_mem_bridge.v, flash_spi_master.v), not
guessed. Covers: physical interfaces, boot/bootstrap (JTAG factory,
Master-SPI autonomous, FLASH_XFER field update), the full management
SPI opcode reference and register map, job submission (including the
critical, real N=16 octet-dispatch requirement -- 8 jobs must share
w_base/n_tiles or the Director's queue stalls permanently with no
in-band recovery), result readback addressing, raw memory access,
config-flash relay with its real 2-dummy-byte timing requirement,
error handling, and honestly disclosed open items (no firmware exists
yet -- this is the specification).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUG92aM9m68TRc4rG55BcC
2026-09-22 08:39:29 +02:00
micheleandClaude Sonnet 5 e3fe007469 docs: reframe primary docs -- N=16 is now the real fabrication target
Physical board fabrication moved to N=16 (with the EXP-0097 MAC-
pipeline fix); v3-artix7 fast-forward merged with n16-timing-closure.
No physical board change needed -- DDR3/SPI/flash/config pins are
identical across all N_GROUPS values. N=2/N=8 kept documented as real,
valid fallback signoffs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUG92aM9m68TRc4rG55BcC
2026-09-22 08:36:37 +02:00
micheleandClaude Sonnet 5 0303aacb9f docs: consolidate N=16 to N=8's own level of rigor (EXP-0098) -- whole family real and closed
N=2 re-verified with the same shared pipelined neural_processor_packed.v
(EXP-0097's fix): real functional xsim 8/8 PASS (identical completion
time to the pre-fix baseline), real full P&R WNS=+0.389ns (up from
+0.099962ns), 0 failing endpoints, 16 DSP48E1 -- no regression.

Real, consolidated family state, all sharing the same pipelined core:
  N=2:  WNS=+0.389ns, 16 DSP48E1,  8/8 functional PASS
  N=8:  WNS=+0.108ns, 64 DSP48E1,  16/16 functional PASS
  N=16: WNS=+0.269ns, 128 DSP48E1, 32/32 functional PASS

Updated the project's own primary real docs (docs/PHYSICAL_REALIZATION.md
S3, docs/ARCHITECTURE_ANALYSIS.md S5.6) to the same depth EXP-0096 gave
N=8 alone -- not just the docs/latex/ deliverables. Clear two-track
framing throughout: the physically fabricated board still uses N=8's
original, unmodified core (WNS=0.000ns exact); this consolidated,
improved family lives on the isolated n16-timing-closure branch,
pending a real hardware decision on whether/when to adopt it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUG92aM9m68TRc4rG55BcC
2026-09-22 01:15:15 +02:00
micheleandClaude Sonnet 5 cc6cfe168e docs: update LaTeX docs for N16 real timing closure + honest ESP32-S3 projection (EXP-0097)
architecture.tex: document the real MAC-pipeline fix (Stage 1a/1b
split) and its real result on both N8 (WNS 0.000ns -> +0.108ns) and
N16 (now closed, WNS=+0.269ns) -- N16 is a real, verified candidate
for a future board revision, N8 remains the configuration in current
physical fabrication.

tests_timing.tex: extended P&R history table, real N8/N16 signoff
tables (post-fix), the new N16 critical path (Director queue logic,
still route-dominated) and the negative result of a second directive
attempt (confirms current result is the best found). Added a new,
clearly-labeled PROJECTION (not a measurement) for N16 vs ESP32-S3 --
30-68x range, built from real measured scaling factors (same core
count as the original ECP5 comparison, real clock ratio, real 2x INT8
packing factor) -- more grounded than the old, superseded ~55-85x
figure, with the same DDR3-bandwidth-bound honest caveat carried
forward.

datasheet.tex: N8/N16 side-by-side timing/utilization/performance
tables, open items and revision history updated to reflect physical
fabrication status and the N16 candidate decision.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUG92aM9m68TRc4rG55BcC
2026-09-22 00:59:46 +02:00
micheleandClaude Sonnet 5 2ab2ff7769 docs: N=8 re-verified with MAC pipeline fix (improves margin), N=16 margin-hunt result (EXP-0097 cont.)
N=8 with the same pipelined neural_processor_packed.v: 16/16 functional
PASS, real P&R WNS=+0.108ns (up from the original exact-zero 0.000ns),
0 failing endpoints, no regression -- the fix is a pure improvement for
N=8 too.

N=16 margin-hunt: worst path moved again (now inside
neural_director_grouped.v's own queue update logic, still route-
dominated) -- confirms the MAC fix resolved its own real bottleneck.
A second real P&R attempt with alternate directives gave WNS=+0.168ns,
worse than the first attempt's +0.269ns -- confirms real P&R variance,
not further systematic gain available without touching Director RTL
(not attempted, current margin already comfortable).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUG92aM9m68TRc4rG55BcC
2026-09-22 00:44:54 +02:00
micheleandClaude Sonnet 5 f9b366d747 feat: N=16 real timing CLOSED via extra MAC pipeline stage (EXP-0097, branch n16-timing-closure)
neural_processor_packed.v: split the original single "Stage 1" (packed
DSP48E1 multiply + INT8 unpack + register) into two real stages --
Stage 1a registers the raw DSP48E1 product with zero logic in between,
Stage 1b does the carry-heavy unpack (the real critical path EXP-0094
traced) from that already-registered value. Adds exactly one real
clock cycle of latency; throughput unaffected (real valid/ready
handshaking throughout, no fixed-latency assumption downstream).

Real verification: isolated bit-exact vs 2x real neural_processor.v
(18/18 PASS, testbench fixed to latch each core's result independently
since result_valid is a one-shot pulse and the DUT is now one cycle
deeper -- not an RTL bug). Full-system functional xsim on real DDR3:
32/32 PASS. Real, full P&R: WNS=+0.269ns, WHS=+0.026ns, 0 failing
setup or hold endpoints -- N=16 TIMING CLOSES.

Also root-caused (not an RTL bug, folded into CLAUDE.md): a real
Vivado incremental-synthesis quirk silently carried forward a
N_GROUPS=2 parameter binding from an earlier sweep run despite no
-generic override and an intervening reset_run -- fixed by always
passing -generic explicitly and confirming the real elaborated value
via a post-synth DSP48E1 count.

Isolated on this branch -- does not touch the physical board already
in fabrication on v3-artix7 (N=8, unmodified).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUG92aM9m68TRc4rG55BcC
2026-09-22 00:17:18 +02:00
10 changed files with 1187 additions and 206 deletions
+17
View File
@@ -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
+82 -20
View File
@@ -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
+386
View File
@@ -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 34), 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.
+83 -37
View File
@@ -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-00950098)
**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
View File
@@ -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
View File
@@ -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
View File
@@ -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}
+227
View File
@@ -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.
+85 -24
View File
@@ -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;
// ============================================================
+44 -16
View File
@@ -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