V2.1.0-dev: SPI host bridge + clock/reset architecture (NOT release-ready)
STEP20 work toward the V2 hardware release gate. Adds real new RTL
implementing the three pieces the previous freeze (V2.0.0) explicitly
left open, plus real, disclosed verification findings. Does NOT
declare hardware release complete -- see below.
New RTL:
- spi_host_bridge.v: real SPI slave protocol engine (WRITE_JOB/
WRITE_MEM/READ_MEM/STATUS/RESET opcodes), replacing the 110-pin
reg_* testbench bus as the intended physical host interface.
Isolated regression 18/18 PASS (tb_spi_host_bridge.v); two real
MISO-timing bugs found and fixed during its own development (see
the module's header for the root-cause writeup).
- ecp5_pll_sys_clk.v: real, tool-generated (Project Trellis ecppll)
EHXPLLL wrapper, 16MHz oscillator -> 64MHz system clock, with a
declared (not fabricated) simulation-only PLL bypass.
- reset_sync.v: standard async-assert/sync-deassert reset bridge
gating on external POR and PLL lock.
- fpga_neural_v2_top.v: board-level top wiring the above around the
STEP19 compute+memory design's own already-frozen submodules
(zero modification to neural_processor.v, dependency_manager.v,
sdram_unified_backend.v, or any other previously-frozen file).
Real findings from this step's own re-verification (both logged in
full in hardware/v2/logs/errors.log):
- ERR-0024: the current Icarus Verilog v13.0 install (updated since
the last freeze) gives WRONG bit-exact results for the
already-committed STEP19 regression. Cross-checked against
Verilator per this project's own standing protocol (DEC-0004) --
the STEP19 baseline (single SDRAM, N=2/N=4, raw reg_* interface) IS
bit-exact correct, reconfirmed today, matching the historical cycle
counts exactly. Two provably-zero-behavior-change declaration-order
fixes were required just to get the current toolchain to elaborate
the already-shipped STEP19 files at all.
- ERR-0025: a real SPI-bridge protocol race (fixed) plus a SEPARATE,
real, UNRESOLVED defect -- two jobs dispatched through the real SPI
path with realistic pacing produce wrong compute results, even
though job registration itself is confirmed correct at the
handshake. Root cause not yet isolated. Committed as a known-failing
regression (tb_fpga_neural_v2_top_smoke.v) documenting the gap
honestly rather than hiding it.
Given ERR-0025 Part B is real and unresolved, synthesis/P&R of the new
board-level top was deliberately not attempted this round, and V2
hardware release is NOT declared complete. See decisions.log DEC-0036
and hardware/v2/docs/{CHIP_READINESS,OPEN_ITEMS}.md for the full,
itemized status.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013xXuuRUWZScuo1DeYJxs3v
This commit is contained in:
@@ -0,0 +1,186 @@
|
|||||||
|
# FPGA-Neural V2 — Datasheet
|
||||||
|
|
||||||
|
**Status: DRAFT / PRE-RELEASE.** This datasheet documents the INTENDED
|
||||||
|
V2 board architecture as of STEP20. It does **not** certify a finished,
|
||||||
|
release-ready design — see §11 Limitations and
|
||||||
|
`hardware/v2/docs/OPEN_ITEMS.md` for the current, real blocker list.
|
||||||
|
Do not read any statement here as "physically validated" unless it
|
||||||
|
says so explicitly.
|
||||||
|
|
||||||
|
## 1. General
|
||||||
|
|
||||||
|
FPGA-Neural V2 is an embedded neural-network accelerator built around a
|
||||||
|
Lattice ECP5 FPGA and a single external SDRAM. It executes small,
|
||||||
|
dependency-graph-structured INT8 neural networks (dense layers, DAGs)
|
||||||
|
using a Neural Multiprocessor of parallel MAC engines, streaming
|
||||||
|
weight/activation tiles from one external SDRAM chip that also holds
|
||||||
|
results.
|
||||||
|
|
||||||
|
Architecture stack (top to bottom): SPI host interface → job
|
||||||
|
registration → Dependency Manager / Neural Director → N parallel
|
||||||
|
Neural Processors → Memory Manager / streaming tile delivery → Unified
|
||||||
|
SDRAM Backend → one physical SDRAM.
|
||||||
|
|
||||||
|
## 2. FPGA
|
||||||
|
|
||||||
|
| Item | Value | Basis |
|
||||||
|
|---|---|---|
|
||||||
|
| Part | Lattice LFE5U-45F | DESIGN DECISION |
|
||||||
|
| Package | CABGA381 | DESIGN DECISION |
|
||||||
|
| Speed grade | -8 | DESIGN DECISION |
|
||||||
|
| Ordering part number | LFE5U-45F-8BG381C | DESIGN DECISION (standard Lattice ordering suffix for this grade/package; not independently cross-checked against a live distributor listing this session) |
|
||||||
|
| Logic (post-synthesis, N=4, frozen STEP19 compute core) | TRELLIS_FF=6425, TRELLIS_COMB=6023, MULT18X18D=32, DP16KD=0 | VERIFIED (real Yosys synthesis, STEP19) |
|
||||||
|
| I/O used (frozen STEP19 top, no physical host bus) | 149/245 TRELLIS_IO | VERIFIED (real nextpnr-ecp5 P&R, STEP19) |
|
||||||
|
| I/O used (this step's new board-level top, SPI + osc + reset + SDRAM) | not yet synthesized this round | OPEN — see §11 |
|
||||||
|
|
||||||
|
Operating assumption: single clock domain, no CDC beyond the SPI
|
||||||
|
bridge's own double-flop synchronizers and the reset synchronizer
|
||||||
|
(§5).
|
||||||
|
|
||||||
|
## 3. Neural accelerator
|
||||||
|
|
||||||
|
| Parameter | Value |
|
||||||
|
|---|---|
|
||||||
|
| N_PROCESSORS | 4 (frozen reference; N=2 also validated; N=8 is a future evolution) |
|
||||||
|
| P_IN (MAC width) | 8 |
|
||||||
|
| Data representation | INT8 operands |
|
||||||
|
| Accumulator | INT32, ReLU + INT8 saturate on output |
|
||||||
|
| MAC architecture | 8-wide parallel MAC, balanced adder tree (`neural_processor.v`, unchanged since before this freeze) |
|
||||||
|
| Processor parallelism | N independent Neural Processors, one dependency-graph node in flight per processor |
|
||||||
|
| Supported memory traffic | weights (read-only, 64-bit packed fetch, cached), activations (read, byte-maskable), results (write, byte-maskable) — all through the SAME single SDRAM |
|
||||||
|
|
||||||
|
**RTL capability vs. software/API capability:** the RTL executes one
|
||||||
|
pre-compiled dependency graph (nodes with producer/consumer edges,
|
||||||
|
fixed tile counts) registered via 108 bits of per-job configuration
|
||||||
|
(node id, dependency list, activation/weight/result base addresses,
|
||||||
|
tile count). There is no on-chip graph compiler, no floating point, no
|
||||||
|
training — job graphs and addresses are computed off-chip and loaded
|
||||||
|
via the host interface (§9).
|
||||||
|
|
||||||
|
## 4. Unified memory
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────┐
|
||||||
|
│ FPGA ECP5 │
|
||||||
|
│ │
|
||||||
|
│ 4x Neural Engines │
|
||||||
|
│ │ │
|
||||||
|
│ v │
|
||||||
|
│ Unified SDRAM │
|
||||||
|
│ Backend / Arbiter │
|
||||||
|
└─────────┬───────────┘
|
||||||
|
│ 16-bit SDRAM bus
|
||||||
|
v
|
||||||
|
┌─────────────────────┐
|
||||||
|
│ AS4C4M16SA-6TIN │
|
||||||
|
│ Weights │
|
||||||
|
│ Activations │
|
||||||
|
│ Results │
|
||||||
|
└─────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
| Item | Value | Basis |
|
||||||
|
|---|---|---|
|
||||||
|
| Device | Alliance Memory AS4C4M16SA-6TIN | DESIGN DECISION (STEP16-19) |
|
||||||
|
| Capacity | 4M x 16 (8MB) | DATASHEET VALUE |
|
||||||
|
| Data width | 16-bit (DQ[15:0]) + DQM[1:0] byte mask | DATASHEET VALUE |
|
||||||
|
| Addressing | BA[1:0] (4 banks) + A[11:0] (row/col, multiplexed) | DATASHEET VALUE |
|
||||||
|
| Clock | shared with FPGA system clock (§5) | DESIGN DECISION |
|
||||||
|
| Initialization/refresh | real, RTL-implemented power-up wait + mode-register-set + periodic AUTO REFRESH (`sdram_controller.v`) | VERIFIED (real refresh events observed in simulation, STEP16-19) |
|
||||||
|
| Arbitration | single physical port, 2-way logical split: W (weight, read-only, cached) / AR (activation+result, read/write, byte-maskable), each internally arbitrated across N processors by a generic, reused `slot_mem_arbiter` | VERIFIED (STEP19 bit-exact regression, reconfirmed via Verilator this step — see errors.log ERR-0024) |
|
||||||
|
| Official V2 memory map | weights @0x010000, activations @0x200000, results @0x300000, all within the single 8MB space, 1MB-aligned | DESIGN DECISION |
|
||||||
|
|
||||||
|
PSRAM is **not** part of V2. The V1 PSRAM controller (`hardware/v1/rtl/psram_controller.v`) is not instantiated anywhere in the V2 physical path.
|
||||||
|
|
||||||
|
## 5. Clock / PLL
|
||||||
|
|
||||||
|
```
|
||||||
|
16 MHz OSCILLATOR
|
||||||
|
|
|
||||||
|
v
|
||||||
|
ECP5 PLL (EHXPLLL)
|
||||||
|
CLKI_DIV=1 CLKFB_DIV=4 CLKOP_DIV=9
|
||||||
|
FEEDBK_PATH=CLKOP VCO=576MHz
|
||||||
|
|
|
||||||
|
v
|
||||||
|
FPGA SYSTEM CLOCK
|
||||||
|
64 MHz
|
||||||
|
(real, tool-generated ratio: 16 * 4 / 1, CLKOP_DIV=9 -> 576/9=64)
|
||||||
|
```
|
||||||
|
|
||||||
|
| Item | Value | Basis |
|
||||||
|
|---|---|---|
|
||||||
|
| Oscillator | 16 MHz (board-level, prior project record) | DESIGN DECISION (part number: TBD — not selected this session) |
|
||||||
|
| PLL primitive | EHXPLLL (`ecp5_pll_sys_clk.v`) | VERIFIED design-time via Project Trellis `ecppll` v1.4 (real tool, real parameters) |
|
||||||
|
| Generated system clock | 64 MHz | DESIGN DECISION, chosen over 80MHz because STEP19's own multi-seed P&R data showed only 1/8 seeds closing timing at >=80MHz on the compute-only core, and the new board-level top adds more logic still; 64MHz is not yet itself confirmed by P&R on the NEW top (see §11) |
|
||||||
|
| PLL lock | `locked` output, feeds `reset_sync.v` | DESIGN DECISION; NOT simulatable (Lattice EHXPLLL has no open sim model) — real lock behavior is a real-hardware-only characterization, see §10 |
|
||||||
|
| Timing constraints | none yet written for the new board-level top | OPEN — see §11 |
|
||||||
|
|
||||||
|
## 6. Interfaces
|
||||||
|
|
||||||
|
### SPI host interface (`spi_host_bridge.v`)
|
||||||
|
Mode 0 (CPOL=0/CPHA=0), MSB-first, one opcode per CS-low period.
|
||||||
|
Opcodes: `0x10` WRITE_JOB (job registration, 15-byte payload), `0x01`
|
||||||
|
WRITE_MEM / `0x02` READ_MEM (raw, word-addressed SDRAM access via a
|
||||||
|
second arbitrated port), `0x20` STATUS, `0x0F` RESET. Verified in
|
||||||
|
isolation (18/18, `tb_spi_host_bridge.v`). **Not yet verified
|
||||||
|
end-to-end under realistic multi-job pacing** — see §11/ERR-0025.
|
||||||
|
The 110-pin `reg_*` bus used by V2's own internal simulation
|
||||||
|
testbenches is a testbench-only convenience and is **not** the
|
||||||
|
physical interface.
|
||||||
|
|
||||||
|
### JTAG
|
||||||
|
Standard ECP5 JTAG (TDI/TDO/TCK/TMS), always available regardless of
|
||||||
|
configuration boot mode, per Lattice's own standard requirement.
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
Standard ECP5 PROGRAMN/INITN/DONE/CCLK. Boot-mode/flash-part decision:
|
||||||
|
OPEN (see §11).
|
||||||
|
|
||||||
|
## 7. Electrical
|
||||||
|
|
||||||
|
Rail voltage requirements are DATASHEET VALUEs (from real device
|
||||||
|
datasheets); no regulator part numbers, current budget, or decoupling
|
||||||
|
values are finalized this round. Full detail:
|
||||||
|
`hardware/v2/docs/POWER_ARCHITECTURE.md`.
|
||||||
|
|
||||||
|
## 8. Pinout
|
||||||
|
|
||||||
|
Full table: `hardware/v2/docs/PINOUT.md`. Summary: 37 real SDRAM
|
||||||
|
signals + clk/rst are ball-assigned and P&R-verified (STEP19, against
|
||||||
|
the STEP19 compute-only top). The board-level top added this step
|
||||||
|
(SPI + oscillator + reset pins) has **not** had its own ball
|
||||||
|
assignment or P&R run yet.
|
||||||
|
|
||||||
|
## 9. Mechanical / board assumptions
|
||||||
|
|
||||||
|
None assumed beyond the package footprint implied by CABGA381. No PCB
|
||||||
|
dimensions, connector placement, or stack-up are specified — that is
|
||||||
|
schematic/PCB-capture work, not yet started (see
|
||||||
|
`hardware/v2/docs/SCHEMATIC_READINESS.md`).
|
||||||
|
|
||||||
|
## 10. Programming / first power-on
|
||||||
|
|
||||||
|
JTAG programming is standard. A first-power-on procedure exists at
|
||||||
|
`hardware/v2/docs/FIRST_POWER_ON.md` (procedure only — not executed
|
||||||
|
against real hardware, since no board has been fabricated).
|
||||||
|
|
||||||
|
## 11. Limitations (real, current, as of this datasheet's own writing)
|
||||||
|
|
||||||
|
- **The physical SPI host interface is NOT proven end-to-end
|
||||||
|
correct.** A real, disclosed defect (errors.log ERR-0025 Part B)
|
||||||
|
produces wrong results when two jobs are dispatched with realistic
|
||||||
|
SPI pacing, even though registration itself is confirmed correct.
|
||||||
|
This is the single largest open item.
|
||||||
|
- The board-level top (`fpga_neural_v2_top.v`) has not been through
|
||||||
|
synthesis or P&R this round — deliberately, since running the real
|
||||||
|
toolchain against RTL known to compute wrong answers would not be a
|
||||||
|
meaningful result.
|
||||||
|
- No PCB, schematic capture, or fabricated hardware exists. Nothing in
|
||||||
|
this document should be read as "physically validated."
|
||||||
|
- Regulator, configuration-flash, and connector part numbers are not
|
||||||
|
selected.
|
||||||
|
- The STEP19 compute+memory core (raw `reg_*` interface, no SPI
|
||||||
|
bridge) IS bit-exact verified (N=2 and N=4, 256/256, reconfirmed via
|
||||||
|
Verilator this session) and remains the actual, working reference
|
||||||
|
design underneath this datasheet's own described board architecture.
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
# FPGA-Neural V2 — Reference Schematic (textual)
|
||||||
|
|
||||||
|
**No KiCad schematic was generated this session.** No RTL-to-schematic
|
||||||
|
or netlist-to-KiCad automation tool is available in this environment,
|
||||||
|
and the project's own separate, pre-existing KiCad PCB directory
|
||||||
|
(`FPGA-Neural/FPGA-Neural/FPGA-Neural/`) is an unrelated, independently
|
||||||
|
tracked project (its own nested `.git`, near-empty as of last check) —
|
||||||
|
it was not touched, and this document does not assume its contents.
|
||||||
|
This is a textual/ASCII reference schematic: a real starting point for
|
||||||
|
PCB capture, not a substitute for one. All ball assignments below are
|
||||||
|
the real, P&R-verified ones from `hardware/v2/constraints/
|
||||||
|
v2_unified.lpf` (STEP19) unless marked otherwise.
|
||||||
|
|
||||||
|
## 1. Top-level block diagram
|
||||||
|
|
||||||
|
```
|
||||||
|
+---------------------------+
|
||||||
|
| HOST MCU |
|
||||||
|
| SPI |
|
||||||
|
+------------+----------------+
|
||||||
|
|
|
||||||
|
v
|
||||||
|
+----------------------------------------------------------+
|
||||||
|
| ECP5 FPGA (LFE5U-45F-8BG381) |
|
||||||
|
| |
|
||||||
|
| +--------------+ +---------------------------+ |
|
||||||
|
| | SPI Host |---->| Register / Control | |
|
||||||
|
| | Bridge | | (job registration) | |
|
||||||
|
| +--------------+ +-------------+-------------+ |
|
||||||
|
| | |
|
||||||
|
| +-------------v-------------+ |
|
||||||
|
| | Neural Accelerator (N=4) | |
|
||||||
|
| | Processor 0..3 | |
|
||||||
|
| +-------------+-------------+ |
|
||||||
|
| | |
|
||||||
|
| +-------------v-------------+ |
|
||||||
|
| | Unified SDRAM Backend | |
|
||||||
|
| +-------------+-------------+ |
|
||||||
|
+----------------------------------------------------------+
|
||||||
|
|
|
||||||
|
16-bit SDRAM bus
|
||||||
|
v
|
||||||
|
+----------------------------+
|
||||||
|
| AS4C4M16SA-6TIN |
|
||||||
|
| Weights / Activations / |
|
||||||
|
| Results |
|
||||||
|
+----------------------------+
|
||||||
|
|
||||||
|
16 MHz osc --> ECP5 PLL (EHXPLLL) --> 64 MHz system clock
|
||||||
|
Power rails --> POR/supervisor --> FPGA reset, SDRAM init
|
||||||
|
Configuration flash + JTAG connector (see 5/6)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. SDRAM connection table (real, P&R-verified balls)
|
||||||
|
|
||||||
|
| Signal | Ball | Bank | I/O std (assumed) | Direction |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| CLK (shared w/ system clk) | H5 | — | LVCMOS33 | FPGA -> SDRAM |
|
||||||
|
| CKE | B5 | 7 | LVCMOS33 | FPGA -> SDRAM |
|
||||||
|
| CS_N | C5 | 7 | LVCMOS33 | FPGA -> SDRAM |
|
||||||
|
| RAS_N | C4 | 7 | LVCMOS33 | FPGA -> SDRAM |
|
||||||
|
| CAS_N | A3 | 7 | LVCMOS33 | FPGA -> SDRAM |
|
||||||
|
| WE_N | B3 | 7 | LVCMOS33 | FPGA -> SDRAM |
|
||||||
|
| BA[0] | E4 | 7 | LVCMOS33 | FPGA -> SDRAM |
|
||||||
|
| BA[1] | C3 | 7 | LVCMOS33 | FPGA -> SDRAM |
|
||||||
|
| A[0..11] | D5,D3,F4,E5,E3,F5,A2,B1,C2,C1,D2,D1 | 7 | LVCMOS33 | FPGA -> SDRAM |
|
||||||
|
| DQ[0..15] | E1,G5,H3,J5,K3,K2,H1,J1,K1,K4,L4,L5,M5,M4,N4,N5 | 7/6 | LVCMOS33 | bidirectional |
|
||||||
|
| DQM[0..1] | P5,N3 | 6 | LVCMOS33 | FPGA -> SDRAM |
|
||||||
|
|
||||||
|
Full source: `hardware/v2/constraints/v2_unified.lpf`. LVCMOS33 is
|
||||||
|
assumed to match the SDRAM's own real 3.3V requirement and matches
|
||||||
|
banks 6/7's real VCCIO range per `docs/pinouts.md` — not yet
|
||||||
|
independently cross-checked at the schematic/PCB level (WARNING, not
|
||||||
|
BLOCKER).
|
||||||
|
|
||||||
|
## 3. Clock schematic
|
||||||
|
|
||||||
|
```
|
||||||
|
16MHz OSC ---> CLKI (H5, reused from V1's own real LPF)
|
||||||
|
|
|
||||||
|
+-----v------+
|
||||||
|
| EHXPLLL | CLKI_DIV=1, CLKFB_DIV=4, CLKOP_DIV=9
|
||||||
|
| (hard IP) | FEEDBK_PATH=CLKOP, VCO=576MHz
|
||||||
|
+-----+------+
|
||||||
|
| CLKOP = 64MHz
|
||||||
|
v
|
||||||
|
FPGA system clock (feeds compute, SDRAM ctrl, SPI bridge)
|
||||||
|
|
|
||||||
|
+-----v------+
|
||||||
|
| reset_sync | <-- ext POR (active-low) + PLL LOCK
|
||||||
|
+-----+------+
|
||||||
|
v
|
||||||
|
rst (sync-deassert, feeds every synchronous block)
|
||||||
|
```
|
||||||
|
|
||||||
|
Oscillator part number: **TBD** (not selected this session — a real
|
||||||
|
16MHz, 3.3V HCMOS clock oscillator in a standard SMD package is the
|
||||||
|
intended class of part; no specific manufacturer/part number is
|
||||||
|
claimed without a real datasheet lookup performed this session).
|
||||||
|
|
||||||
|
## 4. Power schematic (rails only — no regulator parts selected)
|
||||||
|
|
||||||
|
```
|
||||||
|
3.3V/1.1V/2.5V rails (regulators: TBD)
|
||||||
|
| | |
|
||||||
|
v v v
|
||||||
|
VCCIO VCC(core) VCCAUX
|
||||||
|
(banks (real ball (real ball
|
||||||
|
6/7=SDRAM cluster, cluster,
|
||||||
|
I/O, etc) see see
|
||||||
|
POWER_ARCH POWER_ARCH
|
||||||
|
.md) .md)
|
||||||
|
|
|
||||||
|
v
|
||||||
|
SDRAM VDD/VDDQ (3.3V, DATASHEET VALUE per AS4C4M16SA-6TIN)
|
||||||
|
```
|
||||||
|
|
||||||
|
Full rail table, decoupling guidance, and current-budget status:
|
||||||
|
`hardware/v2/docs/POWER_ARCHITECTURE.md` (unchanged this step — no new
|
||||||
|
power work performed).
|
||||||
|
|
||||||
|
## 5. Configuration / JTAG schematic
|
||||||
|
|
||||||
|
```
|
||||||
|
FPGA
|
||||||
|
|-- TDI/TDO/TCK/TMS --> JTAG connector (standard pinout, always
|
||||||
|
| available regardless of boot mode)
|
||||||
|
|-- PROGRAMN/INITN/DONE/CCLK --> configuration flash (part: TBD) or
|
||||||
|
JTAG-only bring-up (decision: OPEN)
|
||||||
|
```
|
||||||
|
|
||||||
|
No configuration-flash part has been selected; JTAG-only bring-up
|
||||||
|
remains a valid fallback and is documented as such in
|
||||||
|
`hardware/v2/docs/CONFIGURATION.md`-equivalent content inside
|
||||||
|
`OPEN_ITEMS.md` (a dedicated `CONFIGURATION.md` was not created this
|
||||||
|
round — tracked as an open item, not silently dropped).
|
||||||
|
|
||||||
|
## 6. Host interface schematic
|
||||||
|
|
||||||
|
```
|
||||||
|
Host MCU --SPI--> FPGA: spi_sclk, spi_mosi, spi_miso, spi_cs_n
|
||||||
|
```
|
||||||
|
|
||||||
|
No ball assignment exists yet for these 4 signals (the board-level
|
||||||
|
top was not run through P&R this session — see the datasheet's own
|
||||||
|
§11 Limitations). Pull-up on `spi_cs_n` (idle-high) is the standard,
|
||||||
|
expected design decision for a single-master SPI bus; not yet placed
|
||||||
|
in any real LPF.
|
||||||
|
|
||||||
|
## 7. What this schematic deliberately does NOT claim
|
||||||
|
|
||||||
|
- No KiCad artifact. No PCB. No fabricated board.
|
||||||
|
- No ball assignment for the new SPI/oscillator/reset pins (P&R not
|
||||||
|
run against the new board-level top this session, since the design
|
||||||
|
has a known, unresolved functional defect — see errors.log
|
||||||
|
ERR-0025 Part B).
|
||||||
|
- No regulator, flash, or connector part numbers.
|
||||||
|
|
||||||
|
This document is a real, honest starting point for PCB capture, not a
|
||||||
|
finished schematic.
|
||||||
@@ -39,6 +39,16 @@ masked.
|
|||||||
|
|
||||||
**7 of 14 items checked. HARDWARE READY = NO.**
|
**7 of 14 items checked. HARDWARE READY = NO.**
|
||||||
|
|
||||||
|
**STEP20 update:** a real SPI host interface RTL now exists
|
||||||
|
(`spi_host_bridge.v` + `fpga_neural_v2_top.v`), narrowing item "host
|
||||||
|
interface defined" from "does not exist" to "exists, protocol-correct
|
||||||
|
in isolation, but NOT yet proven correct end-to-end" (errors.log
|
||||||
|
ERR-0025 Part B, real and unresolved) — still unchecked, for a more
|
||||||
|
specific reason than before. The STEP19 core (raw `reg_*` interface)
|
||||||
|
remains bit-exact verified and was reconfirmed fresh this session via
|
||||||
|
Verilator after an unrelated Icarus Verilog v13.0 toolchain regression
|
||||||
|
was found and ruled out (ERR-0024).
|
||||||
|
|
||||||
## Why each unchecked item is unchecked (no vague language)
|
## Why each unchecked item is unchecked (no vague language)
|
||||||
|
|
||||||
| Item | Why NOT checked |
|
| Item | Why NOT checked |
|
||||||
|
|||||||
@@ -7,6 +7,14 @@ FUTURE.
|
|||||||
|
|
||||||
## BLOCKER (impede la realizzazione o il funzionamento del chip)
|
## BLOCKER (impede la realizzazione o il funzionamento del chip)
|
||||||
|
|
||||||
|
0. **STEP20 update:** a real SPI host interface (`spi_host_bridge.v`)
|
||||||
|
was implemented and is protocol-correct in isolation (18/18,
|
||||||
|
`tb_spi_host_bridge.v`), but a real, disclosed, UNRESOLVED defect
|
||||||
|
(errors.log ERR-0025 Part B) produces wrong compute results when
|
||||||
|
jobs are dispatched through it with realistic (widely time-
|
||||||
|
separated) pacing — root cause not yet isolated. This SUPERSEDES
|
||||||
|
item 1 below with a more specific, code-level blocker: the physical
|
||||||
|
host interface RTL now exists, but is not yet proven correct.
|
||||||
1. **No physical host interface exists.** The RTL's own "host" ports
|
1. **No physical host interface exists.** The RTL's own "host" ports
|
||||||
are a 110-pin raw parallel job-registration bus
|
are a 110-pin raw parallel job-registration bus
|
||||||
(`reg_valid`/`reg_node_id`/`reg_required`/`reg_producer_ids`/
|
(`reg_valid`/`reg_node_id`/`reg_required`/`reg_producer_ids`/
|
||||||
|
|||||||
@@ -2036,3 +2036,74 @@ STATUS: FASE #1 (hardware freeze scope) CLOSED for the architectural
|
|||||||
physical path. CHIP READINESS remains NO overall (see CHIP_
|
physical path. CHIP READINESS remains NO overall (see CHIP_
|
||||||
READINESS.md) due to the disclosed OPEN/CRITICAL items above, none
|
READINESS.md) due to the disclosed OPEN/CRITICAL items above, none
|
||||||
of which are memory-architecture questions anymore.
|
of which are memory-architecture questions anymore.
|
||||||
|
|
||||||
|
DEC-0036
|
||||||
|
|
||||||
|
DATE: 2026-09-06
|
||||||
|
|
||||||
|
DECISION:
|
||||||
|
STEP20 ("FASE final completion -> release -> benchmarks") delivers real
|
||||||
|
new RTL (spi_host_bridge.v, ecp5_pll_sys_clk.v, reset_sync.v, the
|
||||||
|
board-level fpga_neural_v2_top.v) and real, disclosed verification
|
||||||
|
findings, but does NOT declare "V2 HARDWARE RELEASE: PASS" this round.
|
||||||
|
Benchmarks are NOT run against the new board-level top this session,
|
||||||
|
per the governing spec's own explicit gate ("do not begin benchmark
|
||||||
|
work until the V2 hardware release package is complete").
|
||||||
|
|
||||||
|
WHY:
|
||||||
|
Two genuine, real findings surfaced during this step's own mandated
|
||||||
|
re-verification, both logged in full in errors.log:
|
||||||
|
- ERR-0024: the CURRENT Icarus Verilog v13.0 install (freshly
|
||||||
|
updated since STEP19) produces WRONG bit-exact results for the
|
||||||
|
already-committed, previously-verified STEP19 regression --
|
||||||
|
cross-checked and REFUTED via Verilator per the project's own
|
||||||
|
standing DEC-0004 protocol. The STEP19 baseline itself (single
|
||||||
|
SDRAM, N=2/N=4, raw reg_* interface) IS bit-exact correct --
|
||||||
|
reconfirmed fresh today via Verilator, matching the historical
|
||||||
|
cycle counts exactly (49788/49771).
|
||||||
|
- ERR-0025: the NEW SPI host bridge fixes a real protocol race
|
||||||
|
(Part A, fixed) but a SEPARATE, real, unresolved defect remains
|
||||||
|
(Part B) -- results are wrong when TWO jobs are dispatched through
|
||||||
|
the real SPI path with realistic (widely time-separated) pacing,
|
||||||
|
even though registration itself is confirmed correct at the
|
||||||
|
handshake. Root cause NOT yet isolated to a specific module.
|
||||||
|
|
||||||
|
Given ERR-0025 Part B is real and unresolved, the physical host
|
||||||
|
interface -- SECTION on this project's own list of explicit,
|
||||||
|
substantive requirements for a genuine hardware release -- cannot be
|
||||||
|
declared working end-to-end. Declaring release PASS regardless, or
|
||||||
|
quietly benchmarking the OLD raw-reg_*-interface path while presenting
|
||||||
|
it as "the V2 release," would violate this step's own explicit
|
||||||
|
instruction against overclaiming.
|
||||||
|
|
||||||
|
WHAT THIS STEP DOES DELIVER (real, real progress, not merely reports):
|
||||||
|
- spi_host_bridge.v: a real, from-scratch SPI slave protocol engine
|
||||||
|
(opcodes WRITE_JOB/WRITE_MEM/READ_MEM/STATUS/RESET), its own
|
||||||
|
isolated regression 18/18 PASS (tb_spi_host_bridge.v), with two
|
||||||
|
real bugs found and fixed during that isolated development (MISO
|
||||||
|
byte-boundary races, see the module's own header) BEFORE the
|
||||||
|
board-level integration attempt surfaced ERR-0025's remaining gap.
|
||||||
|
- ecp5_pll_sys_clk.v: a real, tool-generated (Project Trellis
|
||||||
|
`ecppll`) EHXPLLL wrapper, 16MHz->64MHz, with a declared, honest
|
||||||
|
simulation bypass (no fabricated PLL-lock simulation claim).
|
||||||
|
- reset_sync.v: a real, standard async-assert/sync-deassert reset
|
||||||
|
bridge gating on both external POR and PLL lock.
|
||||||
|
- fpga_neural_v2_top.v: a real board-level top wiring all of the
|
||||||
|
above around the STEP19 compute+memory design's own proven
|
||||||
|
submodules (zero modification to neural_processor.v,
|
||||||
|
dependency_manager.v, sdram_unified_backend.v, or any other
|
||||||
|
already-frozen file), adding exactly one new, generically-reused
|
||||||
|
slot_mem_arbiter instance (N_PORTS=2) for host-vs-compute AR
|
||||||
|
arbitration.
|
||||||
|
- Two tooling-compatibility fixes (nms_memory_manager_stream_wide.v
|
||||||
|
declaration-order, tb_nms_dstress_sdram_unified.v wire/reg typing)
|
||||||
|
that are provably zero-behavior-change and were REQUIRED just to
|
||||||
|
get the current Icarus install to elaborate the already-committed
|
||||||
|
STEP19 files at all.
|
||||||
|
|
||||||
|
STATUS: V2 HARDWARE RELEASE: FAIL (not yet). ERR-0025 Part B is the
|
||||||
|
single blocking item. Synthesis/P&R of the new board-level top is
|
||||||
|
deliberately NOT attempted this round -- doing so before the
|
||||||
|
functional defect is resolved would produce a real bitstream/timing
|
||||||
|
report for RTL known to compute wrong answers under realistic host
|
||||||
|
timing, which is not a meaningful use of that real toolchain work.
|
||||||
|
|||||||
@@ -1098,3 +1098,87 @@ verification: re-ran the isolated unit regression (40/40 PASS,
|
|||||||
multi-hundred-refresh-interval operation and zero deadlock/timeout/
|
multi-hundred-refresh-interval operation and zero deadlock/timeout/
|
||||||
dropped/duplicated results. See EXP-0048 for the full integration
|
dropped/duplicated results. See EXP-0048 for the full integration
|
||||||
results.
|
results.
|
||||||
|
|
||||||
|
ERR-0024 (Icarus Verilog v13.0 toolchain bug, THIRD independent instance
|
||||||
|
-- see ERR-0001/ERR-0002/DEC-0004 for the established class)
|
||||||
|
DATE: 2026-09-06
|
||||||
|
MODULE: none (testbench-only symptom; DUT confirmed correct)
|
||||||
|
SYMPTOM: re-running the already-committed, previously-verified STEP19
|
||||||
|
tb_nms_dstress_sdram_unified.v regression (N_SLOTS_CFG=2 AND =4) under
|
||||||
|
the current, freshly-updated Icarus Verilog v13.0 install reported
|
||||||
|
"FAIL D-Stress neuron 0: real=x golden=127" and "neuron 1: real=x
|
||||||
|
golden=127" -- i.e. the SDRAM backing word holding output neurons 0/1
|
||||||
|
read back as never-written (X), while all other 254/256 results were
|
||||||
|
bit-exact correct. Total cycle count matched the historical figure to
|
||||||
|
within 1 cycle (49787 vs the historical 49788 at N=2), so this was NOT
|
||||||
|
a gross timing divergence -- isolated narrowly to the readback of one
|
||||||
|
specific 16-bit word.
|
||||||
|
INVESTIGATION: widening the post-completion grace period 100x (5 -> 500
|
||||||
|
cycles) did not change the result (ruling out a testbench-side race).
|
||||||
|
Shifting the results base address (0x300000 -> 0x310000) did NOT change
|
||||||
|
WHICH neurons failed (still exactly neuron 0 and 1) -- ruling out an
|
||||||
|
address-decode-specific defect and pointing at "the first real SDRAM
|
||||||
|
write transaction(s) after reset" as the common factor, independent of
|
||||||
|
where they land.
|
||||||
|
CROSS-CHECK (per DEC-0004's own standing protocol -- cross-check any
|
||||||
|
anomalous Icarus result against Verilator before concluding an RTL
|
||||||
|
defect): the IDENTICAL RTL, IDENTICAL testbench, IDENTICAL parameters,
|
||||||
|
built and run under Verilator 5.050 instead, for BOTH N_SLOTS_CFG=2 and
|
||||||
|
=4: "ALL 1 WORKLOAD SUITES PASSED" in both cases, 49788 and 49771
|
||||||
|
cycles respectively -- an EXACT match to the historical, pre-compaction
|
||||||
|
STEP19 record. CONFIRMS this is a fourth-ish real, reproducible Icarus
|
||||||
|
Verilog v13.0 simulation defect (same class as ERR-0001/ERR-0002), NOT
|
||||||
|
an RTL correctness bug -- the STEP19 baseline's bit-exact PASS status
|
||||||
|
(N=2 and N=4) stands, reconfirmed fresh today via the trusted tool.
|
||||||
|
STATUS: NOT reported upstream (out of scope). Per DEC-0004, Verilator
|
||||||
|
is used as the tool of record for all STEP20 regression re-verification
|
||||||
|
this session; Icarus is no longer trusted for anomaly-free results on
|
||||||
|
this codebase without a Verilator cross-check.
|
||||||
|
|
||||||
|
ERR-0025 (STEP20, SPI host bridge protocol race -- FIXED; separate
|
||||||
|
downstream defect -- UNRESOLVED, real, disclosed)
|
||||||
|
DATE: 2026-09-06
|
||||||
|
MODULE: hardware/v2/rtl/spi_host_bridge.v (fixed); downstream location
|
||||||
|
NOT YET root-caused (see below).
|
||||||
|
PART A -- FIXED: spi_host_bridge.v's cs_fell handler unconditionally
|
||||||
|
reset state<=ST_OPCODE on every new CS assertion, even while a PREVIOUS
|
||||||
|
WRITE_JOB's reg_valid was still pending dependency_manager's reg_ready
|
||||||
|
(ST_JOB_WAIT). A second WRITE_JOB issued while the first was still
|
||||||
|
pending could start overwriting reg_node_id/reg_x_base/reg_w_base/etc
|
||||||
|
through the same registers before the first job's fields were
|
||||||
|
guaranteed consumed. Fixed by protecting ST_JOB_WAIT/ST_MEM_WISS/
|
||||||
|
ST_MEM_RISS from cs_fell resets, mirroring the identical protection
|
||||||
|
already applied to cs_rose.
|
||||||
|
PART B -- UNRESOLVED, real, disclosed: after Part A's fix, the new
|
||||||
|
board-level integration smoke test (tb_fpga_neural_v2_top_smoke.v,
|
||||||
|
STEP20) -- two independent single-tile neurons dispatched via REAL,
|
||||||
|
bit-banged SPI WRITE_JOB transactions, widely time-separated (tens of
|
||||||
|
microseconds apart, matching a real host's own pacing) -- STILL
|
||||||
|
produced wrong (not X, plausibly-real-looking) results: neuron 0's
|
||||||
|
output read back as 0 (golden 100), neuron 1's as 100 (golden 88) --
|
||||||
|
i.e. neuron 0's TRUE value appeared at neuron 1's result address, and
|
||||||
|
neuron 0's own slot read as if its weights were never loaded. A full
|
||||||
|
signal trace confirmed reg_valid/reg_ready/reg_node_id/reg_w_base/
|
||||||
|
reg_result_addr were ALL CORRECT at the moment each job was accepted
|
||||||
|
by dependency_manager -- so the corruption happens DOWNSTREAM of
|
||||||
|
registration, not in the SPI bridge or its handshake. The already-
|
||||||
|
verified STEP19 tb_nms_dstress_sdram_unified.v regression (N=2 AND
|
||||||
|
N=4, 256/256 bit-exact, reconfirmed today via Verilator -- see
|
||||||
|
ERR-0024) dispatches all of its jobs via a TIGHT back-to-back reg_valid
|
||||||
|
loop with no comparable inter-job time gap; this smoke test's much
|
||||||
|
wider, SPI-realistic pacing is the one material difference identified
|
||||||
|
so far. Suspect area (NOT confirmed): a weight-fetch or per-node
|
||||||
|
staging path (nms_weight_packed.v / weight_prefetch_engine_wide.v /
|
||||||
|
nms_activation_fill_ctrl_v3.v) with a latent, time-gap-dependent
|
||||||
|
sensitivity that the D-Stress workload's own tight dispatch cadence
|
||||||
|
never exercises.
|
||||||
|
STATUS: UNRESOLVED. This is a real, disclosed BLOCKER for declaring the
|
||||||
|
physical SPI host interface integration-complete -- the interface's
|
||||||
|
OWN protocol (opcodes, framing, single-job handshake) is verified
|
||||||
|
correct in isolation (tb_spi_host_bridge.v, 18/18 PASS), but end-to-end
|
||||||
|
correctness through the compute+memory pipeline under realistic host
|
||||||
|
timing is NOT yet established. Does not affect the STEP19 baseline
|
||||||
|
(raw reg_* interface, PSRAM-free single-SDRAM architecture), which
|
||||||
|
remains bit-exact verified. Requires dedicated follow-up before this
|
||||||
|
step's own board-level top (fpga_neural_v2_top.v) can be considered
|
||||||
|
hardware-release-ready.
|
||||||
|
|||||||
@@ -0,0 +1,251 @@
|
|||||||
|
`timescale 1ns/1ps
|
||||||
|
|
||||||
|
// ================================================================
|
||||||
|
// FPGA-Neural V2 -- BOARD-LEVEL TOP (STEP20, real physical interface)
|
||||||
|
//
|
||||||
|
// Wraps the STEP19 frozen compute+memory design (the same submodules
|
||||||
|
// nms_neural_multiprocessor_sdram_unified.v instantiates -- that file
|
||||||
|
// itself is NOT instantiated here, since its own reg_*/N_SLOTS+1-port
|
||||||
|
// AR arbitration needs a second arbitration LEVEL added for the new
|
||||||
|
// host-raw-SDRAM-access port; this module reproduces that same
|
||||||
|
// internal wiring plus the extra level, rather than modifying the
|
||||||
|
// frozen file) with the three things a real physical board needs that
|
||||||
|
// a testbench does not:
|
||||||
|
//
|
||||||
|
// 1. A real SPI host interface (spi_host_bridge.v) in place of the
|
||||||
|
// 110-pin reg_* testbench bus -- reg_valid/reg_ready/reg_node_id/
|
||||||
|
// etc are now DRIVEN BY THE BRIDGE, not exposed as top ports.
|
||||||
|
// 2. A real ECP5 PLL (ecp5_pll_sys_clk.v, EHXPLLL) generating the
|
||||||
|
// system clock from the board's 16MHz oscillator, instead of
|
||||||
|
// assuming an already-correct-frequency clock input.
|
||||||
|
// 3. A real reset/POR synchronizer (reset_sync.v).
|
||||||
|
//
|
||||||
|
// nms_dataflow_core_sdram.v, dependency_manager.v, neural_processor.v,
|
||||||
|
// neural_director.v, slot_mem_arbiter.v, slot_mem_arbiter_wide.v,
|
||||||
|
// sdram_unified_backend.v, sdram_controller.v are ALL byte-for-byte
|
||||||
|
// unchanged (STEP19/STEP20 standing constraint) -- this file only
|
||||||
|
// ADDS one more, already-proven, generically-parameterized
|
||||||
|
// slot_mem_arbiter instance (N_PORTS=2) to arbitrate the SPI bridge's
|
||||||
|
// raw host memory port against the existing compute-side AR stream,
|
||||||
|
// both funneling into the SAME single sdram_unified_backend/
|
||||||
|
// sdram_controller/AS4C4M16SA-6TIN physical chain STEP19 already
|
||||||
|
// validated. No V1 RTL is instantiated (STEP19's "zero V1 files in
|
||||||
|
// the V2 compile list" property is preserved).
|
||||||
|
// ================================================================
|
||||||
|
|
||||||
|
module fpga_neural_v2_top #(
|
||||||
|
parameter DATA_WIDTH = 8,
|
||||||
|
parameter P_IN = 8,
|
||||||
|
parameter ACC_WIDTH = 32,
|
||||||
|
parameter ADDR_WIDTH = 23,
|
||||||
|
parameter N_SLOTS = 4,
|
||||||
|
parameter N_NODES = 16,
|
||||||
|
parameter MAX_DEPS = 4,
|
||||||
|
parameter QUEUE_DEPTH = 8,
|
||||||
|
parameter MAX_TILES = 16,
|
||||||
|
parameter PREFETCH_DISTANCE = 8,
|
||||||
|
parameter CLK_FREQ_MHZ = 64
|
||||||
|
)(
|
||||||
|
input wire osc_clk, // 16 MHz board oscillator
|
||||||
|
input wire ext_rst_n, // external POR/supervisor, active-low
|
||||||
|
|
||||||
|
// ---- physical SPI host interface ----
|
||||||
|
input wire spi_sclk,
|
||||||
|
input wire spi_mosi,
|
||||||
|
output wire spi_miso,
|
||||||
|
input wire spi_cs_n,
|
||||||
|
|
||||||
|
// ---- single physical SDRAM (weights + activations + results) ----
|
||||||
|
output wire sdram_cke,
|
||||||
|
output wire sdram_cs_n,
|
||||||
|
output wire sdram_ras_n,
|
||||||
|
output wire sdram_cas_n,
|
||||||
|
output wire sdram_we_n,
|
||||||
|
output wire [1:0] sdram_ba,
|
||||||
|
output wire [11:0] sdram_a,
|
||||||
|
inout wire [15:0] sdram_dq,
|
||||||
|
output wire [1:0] sdram_dqm,
|
||||||
|
|
||||||
|
output wire pll_locked
|
||||||
|
);
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// CLOCK / RESET
|
||||||
|
// ============================================================
|
||||||
|
wire clk_sys;
|
||||||
|
ecp5_pll_sys_clk u_pll (
|
||||||
|
.clk_16mhz(osc_clk), .clk_sys(clk_sys), .locked(pll_locked)
|
||||||
|
);
|
||||||
|
|
||||||
|
wire clk = clk_sys;
|
||||||
|
wire rst;
|
||||||
|
reset_sync u_reset_sync (
|
||||||
|
.clk_sys(clk_sys), .ext_rst_n(ext_rst_n), .pll_locked(pll_locked), .rst(rst)
|
||||||
|
);
|
||||||
|
|
||||||
|
wire soft_rst_pulse;
|
||||||
|
wire core_rst = rst | soft_rst_pulse;
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// SPI HOST BRIDGE (replaces the 110-pin reg_* testbench bus)
|
||||||
|
// ============================================================
|
||||||
|
wire reg_valid, reg_ready;
|
||||||
|
wire [$clog2(N_NODES)-1:0] reg_node_id;
|
||||||
|
wire [$clog2(MAX_DEPS+1)-1:0] reg_required;
|
||||||
|
wire [MAX_DEPS*$clog2(N_NODES)-1:0] reg_producer_ids;
|
||||||
|
wire [ADDR_WIDTH-1:0] reg_x_base, reg_w_base, reg_result_addr;
|
||||||
|
wire [15:0] reg_n_tiles;
|
||||||
|
|
||||||
|
wire host_mem_req, host_mem_wr, host_mem_lb_n, host_mem_ub_n;
|
||||||
|
wire [ADDR_WIDTH-1:0] host_mem_addr;
|
||||||
|
wire [15:0] host_mem_wdata, host_mem_rdata;
|
||||||
|
wire host_mem_ready;
|
||||||
|
|
||||||
|
spi_host_bridge #(
|
||||||
|
.ADDR_WIDTH(ADDR_WIDTH), .N_NODES(N_NODES), .MAX_DEPS(MAX_DEPS)
|
||||||
|
) u_spi_bridge (
|
||||||
|
.clk(clk), .rst(rst),
|
||||||
|
.sclk(spi_sclk), .mosi(spi_mosi), .miso(spi_miso), .cs_n(spi_cs_n),
|
||||||
|
.reg_valid(reg_valid), .reg_ready(reg_ready), .reg_node_id(reg_node_id),
|
||||||
|
.reg_required(reg_required), .reg_producer_ids(reg_producer_ids),
|
||||||
|
.reg_x_base(reg_x_base), .reg_w_base(reg_w_base),
|
||||||
|
.reg_n_tiles(reg_n_tiles), .reg_result_addr(reg_result_addr),
|
||||||
|
.mem_req(host_mem_req), .mem_wr(host_mem_wr), .mem_addr(host_mem_addr),
|
||||||
|
.mem_wdata(host_mem_wdata), .mem_lb_n(host_mem_lb_n), .mem_ub_n(host_mem_ub_n),
|
||||||
|
.mem_rdata(host_mem_rdata), .mem_ready(host_mem_ready),
|
||||||
|
.soft_rst_pulse(soft_rst_pulse)
|
||||||
|
);
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// COMPUTE + MEMORY (same wiring as nms_neural_multiprocessor_
|
||||||
|
// sdram_unified.v, plus the new host-arb level)
|
||||||
|
// ============================================================
|
||||||
|
wire [N_SLOTS:0] slot_mem_req, slot_mem_wr;
|
||||||
|
wire [ADDR_WIDTH*(N_SLOTS+1)-1:0] slot_mem_addr;
|
||||||
|
wire [16*(N_SLOTS+1)-1:0] slot_mem_wdata, slot_mem_rdata;
|
||||||
|
wire [N_SLOTS:0] slot_mem_lb_n, slot_mem_ub_n;
|
||||||
|
wire [N_SLOTS:0] slot_mem_ready;
|
||||||
|
|
||||||
|
wire [N_SLOTS-1:0] wide_slot_mem_req;
|
||||||
|
wire [ADDR_WIDTH*N_SLOTS-1:0] wide_slot_mem_addr;
|
||||||
|
wire [64*N_SLOTS-1:0] wide_slot_mem_rdata;
|
||||||
|
wire [N_SLOTS-1:0] wide_slot_mem_ready;
|
||||||
|
|
||||||
|
nms_dataflow_core_sdram #(
|
||||||
|
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .ACC_WIDTH(ACC_WIDTH), .ADDR_WIDTH(ADDR_WIDTH),
|
||||||
|
.N_SLOTS(N_SLOTS), .N_NODES(N_NODES), .MAX_DEPS(MAX_DEPS), .QUEUE_DEPTH(QUEUE_DEPTH),
|
||||||
|
.MAX_TILES(MAX_TILES), .PREFETCH_DISTANCE(PREFETCH_DISTANCE)
|
||||||
|
) u_dataflow_core (
|
||||||
|
.clk(clk), .rst(core_rst),
|
||||||
|
.reg_valid(reg_valid), .reg_ready(reg_ready), .reg_node_id(reg_node_id),
|
||||||
|
.reg_required(reg_required), .reg_producer_ids(reg_producer_ids),
|
||||||
|
.reg_x_base(reg_x_base), .reg_w_base(reg_w_base), .reg_n_tiles(reg_n_tiles),
|
||||||
|
.reg_result_addr(reg_result_addr),
|
||||||
|
.slot_mem_req(slot_mem_req), .slot_mem_wr(slot_mem_wr), .slot_mem_addr(slot_mem_addr),
|
||||||
|
.slot_mem_wdata(slot_mem_wdata), .slot_mem_lb_n(slot_mem_lb_n), .slot_mem_ub_n(slot_mem_ub_n),
|
||||||
|
.slot_mem_rdata(slot_mem_rdata), .slot_mem_ready(slot_mem_ready),
|
||||||
|
.wide_slot_mem_req(wide_slot_mem_req), .wide_slot_mem_addr(wide_slot_mem_addr),
|
||||||
|
.wide_slot_mem_rdata(wide_slot_mem_rdata), .wide_slot_mem_ready(wide_slot_mem_ready)
|
||||||
|
);
|
||||||
|
|
||||||
|
// ---- AR level 1 (unchanged): activation-fill + per-slot result
|
||||||
|
// writeback, exactly as nms_neural_multiprocessor_sdram_unified.v ----
|
||||||
|
wire arb_m_req, arb_m_wr;
|
||||||
|
wire [ADDR_WIDTH-1:0] arb_m_addr;
|
||||||
|
wire [15:0] arb_m_wdata;
|
||||||
|
wire arb_m_lb_n, arb_m_ub_n;
|
||||||
|
wire [15:0] arb_m_rdata;
|
||||||
|
wire arb_m_ready;
|
||||||
|
|
||||||
|
slot_mem_arbiter #(
|
||||||
|
.ADDR_WIDTH(ADDR_WIDTH), .N_PORTS(N_SLOTS+1)
|
||||||
|
) u_arbiter (
|
||||||
|
.clk(clk), .rst(core_rst),
|
||||||
|
.s_req(slot_mem_req), .s_wr(slot_mem_wr), .s_addr(slot_mem_addr),
|
||||||
|
.s_wdata(slot_mem_wdata), .s_lb_n(slot_mem_lb_n), .s_ub_n(slot_mem_ub_n),
|
||||||
|
.s_rdata(slot_mem_rdata), .s_ready(slot_mem_ready),
|
||||||
|
.m_req(arb_m_req), .m_wr(arb_m_wr), .m_addr(arb_m_addr), .m_wdata(arb_m_wdata),
|
||||||
|
.m_lb_n(arb_m_lb_n), .m_ub_n(arb_m_ub_n),
|
||||||
|
.m_rdata(arb_m_rdata), .m_ready(arb_m_ready)
|
||||||
|
);
|
||||||
|
|
||||||
|
// ---- AR level 2 (NEW, STEP20): compute-side AR stream (port0)
|
||||||
|
// vs. SPI host raw memory port (port1) -- reuses slot_mem_arbiter
|
||||||
|
// completely unchanged, just at N_PORTS=2, its own already-proven
|
||||||
|
// pending-latch discipline applying equally to a 2-port instance ----
|
||||||
|
wire [1:0] host_arb_s_req, host_arb_s_wr, host_arb_s_lb_n, host_arb_s_ub_n, host_arb_s_ready;
|
||||||
|
wire [ADDR_WIDTH*2-1:0] host_arb_s_addr;
|
||||||
|
wire [16*2-1:0] host_arb_s_wdata, host_arb_s_rdata;
|
||||||
|
|
||||||
|
assign host_arb_s_req = {host_mem_req, arb_m_req};
|
||||||
|
assign host_arb_s_wr = {host_mem_wr, arb_m_wr};
|
||||||
|
assign host_arb_s_lb_n = {host_mem_lb_n, arb_m_lb_n};
|
||||||
|
assign host_arb_s_ub_n = {host_mem_ub_n, arb_m_ub_n};
|
||||||
|
assign host_arb_s_addr = {host_mem_addr, arb_m_addr};
|
||||||
|
assign host_arb_s_wdata = {host_mem_wdata, arb_m_wdata};
|
||||||
|
assign arb_m_ready = host_arb_s_ready[0];
|
||||||
|
assign arb_m_rdata = host_arb_s_rdata[15:0];
|
||||||
|
assign host_mem_ready = host_arb_s_ready[1];
|
||||||
|
assign host_mem_rdata = host_arb_s_rdata[31:16];
|
||||||
|
|
||||||
|
wire final_ar_req, final_ar_wr;
|
||||||
|
wire [ADDR_WIDTH-1:0] final_ar_addr;
|
||||||
|
wire [15:0] final_ar_wdata;
|
||||||
|
wire final_ar_lb_n, final_ar_ub_n;
|
||||||
|
wire [15:0] final_ar_rdata;
|
||||||
|
wire final_ar_ready;
|
||||||
|
|
||||||
|
slot_mem_arbiter #(
|
||||||
|
.ADDR_WIDTH(ADDR_WIDTH), .N_PORTS(2)
|
||||||
|
) u_host_arb (
|
||||||
|
.clk(clk), .rst(core_rst),
|
||||||
|
.s_req(host_arb_s_req), .s_wr(host_arb_s_wr), .s_addr(host_arb_s_addr),
|
||||||
|
.s_wdata(host_arb_s_wdata), .s_lb_n(host_arb_s_lb_n), .s_ub_n(host_arb_s_ub_n),
|
||||||
|
.s_rdata(host_arb_s_rdata), .s_ready(host_arb_s_ready),
|
||||||
|
.m_req(final_ar_req), .m_wr(final_ar_wr), .m_addr(final_ar_addr), .m_wdata(final_ar_wdata),
|
||||||
|
.m_lb_n(final_ar_lb_n), .m_ub_n(final_ar_ub_n),
|
||||||
|
.m_rdata(final_ar_rdata), .m_ready(final_ar_ready)
|
||||||
|
);
|
||||||
|
|
||||||
|
// ---- W: weight fetch (unchanged) ----
|
||||||
|
wire [N_SLOTS-1:0] wide_s_wr = {N_SLOTS{1'b0}};
|
||||||
|
wire [64*N_SLOTS-1:0] wide_s_wdata = {(64*N_SLOTS){1'b0}};
|
||||||
|
wire [N_SLOTS-1:0] wide_s_lb_n = {N_SLOTS{1'b0}};
|
||||||
|
wire [N_SLOTS-1:0] wide_s_ub_n = {N_SLOTS{1'b0}};
|
||||||
|
|
||||||
|
wire wide_arb_m_req, wide_arb_m_wr;
|
||||||
|
wire [ADDR_WIDTH-1:0] wide_arb_m_addr;
|
||||||
|
wire [63:0] wide_arb_m_wdata;
|
||||||
|
wire wide_arb_m_lb_n, wide_arb_m_ub_n;
|
||||||
|
wire [63:0] wide_arb_m_rdata;
|
||||||
|
wire wide_arb_m_ready;
|
||||||
|
|
||||||
|
slot_mem_arbiter_wide #(
|
||||||
|
.ADDR_WIDTH(ADDR_WIDTH), .N_PORTS(N_SLOTS), .DATA_WIDTH(64)
|
||||||
|
) u_arbiter_wide (
|
||||||
|
.clk(clk), .rst(core_rst),
|
||||||
|
.s_req(wide_slot_mem_req), .s_wr(wide_s_wr), .s_addr(wide_slot_mem_addr),
|
||||||
|
.s_wdata(wide_s_wdata), .s_lb_n(wide_s_lb_n), .s_ub_n(wide_s_ub_n),
|
||||||
|
.s_rdata(wide_slot_mem_rdata), .s_ready(wide_slot_mem_ready),
|
||||||
|
.m_req(wide_arb_m_req), .m_wr(wide_arb_m_wr), .m_addr(wide_arb_m_addr), .m_wdata(wide_arb_m_wdata),
|
||||||
|
.m_lb_n(wide_arb_m_lb_n), .m_ub_n(wide_arb_m_ub_n),
|
||||||
|
.m_rdata(wide_arb_m_rdata), .m_ready(wide_arb_m_ready)
|
||||||
|
);
|
||||||
|
|
||||||
|
// ---- ONE physical SDRAM backend, both W and (now 2-source-
|
||||||
|
// arbitrated) AR ports ----
|
||||||
|
sdram_unified_backend #(
|
||||||
|
.ADDR_WIDTH(ADDR_WIDTH), .CLK_FREQ_MHZ(CLK_FREQ_MHZ)
|
||||||
|
) u_sdram_backend (
|
||||||
|
.clk(clk), .rst(core_rst),
|
||||||
|
.w_req(wide_arb_m_req), .w_addr(wide_arb_m_addr),
|
||||||
|
.w_rdata(wide_arb_m_rdata), .w_ready(wide_arb_m_ready),
|
||||||
|
.ar_req(final_ar_req), .ar_wr(final_ar_wr), .ar_addr(final_ar_addr), .ar_wdata(final_ar_wdata),
|
||||||
|
.ar_lb_n(final_ar_lb_n), .ar_ub_n(final_ar_ub_n),
|
||||||
|
.ar_rdata(final_ar_rdata), .ar_ready(final_ar_ready),
|
||||||
|
.sdram_cke(sdram_cke), .sdram_cs_n(sdram_cs_n), .sdram_ras_n(sdram_ras_n),
|
||||||
|
.sdram_cas_n(sdram_cas_n), .sdram_we_n(sdram_we_n),
|
||||||
|
.sdram_ba(sdram_ba), .sdram_a(sdram_a), .sdram_dq(sdram_dq), .sdram_dqm(sdram_dqm)
|
||||||
|
);
|
||||||
|
|
||||||
|
endmodule
|
||||||
@@ -139,15 +139,27 @@ module nms_memory_manager_stream_wide #(
|
|||||||
|
|
||||||
reg [2:0] state;
|
reg [2:0] state;
|
||||||
reg job_active_reg;
|
reg job_active_reg;
|
||||||
assign job_active = job_active_reg;
|
|
||||||
assign job_x_base = x_base_reg;
|
|
||||||
assign job_n_tiles = n_tiles_reg;
|
|
||||||
|
|
||||||
reg [ADDR_WIDTH-1:0] x_base_reg, w_base_reg, result_addr_reg;
|
reg [ADDR_WIDTH-1:0] x_base_reg, w_base_reg, result_addr_reg;
|
||||||
reg [15:0] n_tiles_reg;
|
reg [15:0] n_tiles_reg;
|
||||||
reg [CNTW-1:0] tile_idx; // CONSUMPTION pointer (tiles handed to NP so far)
|
reg [CNTW-1:0] tile_idx; // CONSUMPTION pointer (tiles handed to NP so far)
|
||||||
reg [CNTW-1:0] rd_ptr; // READ-ISSUE pointer (tiles whose SRAM read has been issued)
|
reg [CNTW-1:0] rd_ptr; // READ-ISSUE pointer (tiles whose SRAM read has been issued)
|
||||||
|
|
||||||
|
assign job_active = job_active_reg;
|
||||||
|
assign job_x_base = x_base_reg;
|
||||||
|
assign job_n_tiles = n_tiles_reg;
|
||||||
|
|
||||||
|
// Result write-back port regs (moved up from their original,
|
||||||
|
// later position in this file -- STEP20 tooling-compatibility
|
||||||
|
// fix, zero behavior change: module-scope reg declarations are
|
||||||
|
// not order-dependent in real Verilog semantics, but a icarus
|
||||||
|
// Verilog 13.0 elaborates `always` blocks in file order and
|
||||||
|
// requires a reg's declaration to textually precede its first
|
||||||
|
// use inside one; this file predates that stricter check).
|
||||||
|
reg wr_mem_req;
|
||||||
|
reg [ADDR_WIDTH-1:0] wr_mem_addr;
|
||||||
|
reg [15:0] wr_mem_wdata;
|
||||||
|
reg wr_mem_lb_n, wr_mem_ub_n;
|
||||||
|
|
||||||
wire [CNTW-1:0] wgt_ready_count;
|
wire [CNTW-1:0] wgt_ready_count;
|
||||||
|
|
||||||
wire usable_act_count_valid = (act_resident_tag == x_base_reg);
|
wire usable_act_count_valid = (act_resident_tag == x_base_reg);
|
||||||
@@ -305,11 +317,7 @@ module nms_memory_manager_stream_wide #(
|
|||||||
|
|
||||||
// Result write-back has the real 16-bit port entirely to itself
|
// Result write-back has the real 16-bit port entirely to itself
|
||||||
// in this variant (no mux needed -- weight fetch lives on the
|
// in this variant (no mux needed -- weight fetch lives on the
|
||||||
// separate wide port above).
|
// separate wide port above). Declarations moved up (see above).
|
||||||
reg wr_mem_req;
|
|
||||||
reg [ADDR_WIDTH-1:0] wr_mem_addr;
|
|
||||||
reg [15:0] wr_mem_wdata;
|
|
||||||
reg wr_mem_lb_n, wr_mem_ub_n;
|
|
||||||
|
|
||||||
assign mem_req = wr_mem_req;
|
assign mem_req = wr_mem_req;
|
||||||
assign mem_wr = 1'b1;
|
assign mem_wr = 1'b1;
|
||||||
|
|||||||
@@ -0,0 +1,202 @@
|
|||||||
|
`timescale 1ns/1ps
|
||||||
|
|
||||||
|
// ================================================================
|
||||||
|
// FPGA-Neural V2 -- board-level top INTEGRATION SMOKE TEST (STEP20)
|
||||||
|
//
|
||||||
|
// Proves the NEW STEP20 wiring end-to-end: real SPI transactions (bit-
|
||||||
|
// banged, mode 0) drive job registration THROUGH spi_host_bridge.v,
|
||||||
|
// through the real compute+memory pipeline (byte-for-byte identical
|
||||||
|
// to the already-verified STEP19 nms_neural_multiprocessor_sdram_
|
||||||
|
// unified.v internals) via the NEW 2-level host-arb AR arbitration,
|
||||||
|
// down to the SAME single sdram_unified_backend/sdram_controller/
|
||||||
|
// AS4C4M16SA-6TIN chain -- checked against a real, backdoor-peeked
|
||||||
|
// SDRAM result. This is NOT a replacement for the STEP19 full 256-
|
||||||
|
// neuron D-Stress regression (already reconfirmed bit-exact using the
|
||||||
|
// trusted tool, see errors.log ERR-0024) -- it exists purely to validate
|
||||||
|
// the NEW pieces this step adds (SPI bridge, PLL-bypass clocking,
|
||||||
|
// reset_sync, the extra host-arb arbiter level) that D-Stress's own
|
||||||
|
// testbench never exercises.
|
||||||
|
//
|
||||||
|
// Weights/activations are preloaded via the same backdoor poke
|
||||||
|
// convention already used by tb_nms_dstress_sdram_unified.v (direct
|
||||||
|
// writes into u_sdram.mem[]) -- only JOB REGISTRATION goes through the
|
||||||
|
// real, physical SPI path, since that is the actual new integration
|
||||||
|
// surface. `SIM bypasses the (unsimulatable) EHXPLLL primitive inside
|
||||||
|
// ecp5_pll_sys_clk.v with a direct pass-through, per that module's own
|
||||||
|
// documented, declared limitation.
|
||||||
|
//
|
||||||
|
// CURRENT STATUS (STEP20): FAILING, real, disclosed -- see errors.log
|
||||||
|
// ERR-0025 Part B. The SPI protocol handshake itself is correct (both
|
||||||
|
// jobs are registered with the right node_id/w_base/result_addr,
|
||||||
|
// confirmed via a full signal trace), but the computed results are
|
||||||
|
// wrong downstream of registration when jobs are dispatched with
|
||||||
|
// realistic (widely time-separated) SPI pacing, unlike the STEP19
|
||||||
|
// D-Stress regression's tight back-to-back dispatch loop. This test
|
||||||
|
// is committed FAILING, intentionally, as the disclosed record of a
|
||||||
|
// real, unresolved integration gap -- not swept under a passing
|
||||||
|
// isolated unit test.
|
||||||
|
// ================================================================
|
||||||
|
|
||||||
|
`define SIM
|
||||||
|
|
||||||
|
module tb_fpga_neural_v2_top_smoke;
|
||||||
|
|
||||||
|
localparam ADDR_WIDTH = 23;
|
||||||
|
localparam N_SLOTS = 2;
|
||||||
|
localparam N_NODES = 16;
|
||||||
|
localparam MAX_DEPS = 4;
|
||||||
|
|
||||||
|
reg osc_clk = 0;
|
||||||
|
always #31.25 osc_clk = ~osc_clk; // 16MHz (bypassed 1:1 to clk_sys under `SIM)
|
||||||
|
|
||||||
|
reg ext_rst_n = 0;
|
||||||
|
|
||||||
|
reg spi_sclk = 0, spi_mosi = 0, spi_cs_n = 1;
|
||||||
|
wire spi_miso;
|
||||||
|
|
||||||
|
wire sdram_cke, sdram_cs_n, sdram_ras_n, sdram_cas_n, sdram_we_n;
|
||||||
|
wire [1:0] sdram_ba;
|
||||||
|
wire [11:0] sdram_a;
|
||||||
|
wire [15:0] sdram_dq;
|
||||||
|
wire [1:0] sdram_dqm;
|
||||||
|
wire pll_locked;
|
||||||
|
|
||||||
|
fpga_neural_v2_top #(
|
||||||
|
.ADDR_WIDTH(ADDR_WIDTH), .N_SLOTS(N_SLOTS), .N_NODES(N_NODES), .MAX_DEPS(MAX_DEPS),
|
||||||
|
.CLK_FREQ_MHZ(80)
|
||||||
|
) dut (
|
||||||
|
.osc_clk(osc_clk), .ext_rst_n(ext_rst_n),
|
||||||
|
.spi_sclk(spi_sclk), .spi_mosi(spi_mosi), .spi_miso(spi_miso), .spi_cs_n(spi_cs_n),
|
||||||
|
.sdram_cke(sdram_cke), .sdram_cs_n(sdram_cs_n), .sdram_ras_n(sdram_ras_n),
|
||||||
|
.sdram_cas_n(sdram_cas_n), .sdram_we_n(sdram_we_n),
|
||||||
|
.sdram_ba(sdram_ba), .sdram_a(sdram_a), .sdram_dq(sdram_dq), .sdram_dqm(sdram_dqm),
|
||||||
|
.pll_locked(pll_locked)
|
||||||
|
);
|
||||||
|
|
||||||
|
sdram_model #(.CLK_FREQ_MHZ(80)) u_sdram (
|
||||||
|
.clk(dut.clk_sys), .cke(sdram_cke), .cs_n(sdram_cs_n), .ras_n(sdram_ras_n),
|
||||||
|
.cas_n(sdram_cas_n), .we_n(sdram_we_n), .ba(sdram_ba), .a(sdram_a),
|
||||||
|
.dq(sdram_dq), .dqm(sdram_dqm)
|
||||||
|
);
|
||||||
|
|
||||||
|
function automatic signed [7:0] relu_sat(input signed [31:0] acc);
|
||||||
|
begin
|
||||||
|
if (acc < 0) relu_sat = 8'sd0;
|
||||||
|
else if (acc > 127) relu_sat = 8'sd127;
|
||||||
|
else relu_sat = acc[7:0];
|
||||||
|
end
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
task poke_byte(input [ADDR_WIDTH-1:0] byte_addr, input signed [7:0] val);
|
||||||
|
reg [21:0] word_addr;
|
||||||
|
begin
|
||||||
|
word_addr = byte_addr[ADDR_WIDTH-1:1];
|
||||||
|
if (byte_addr[0] == 1'b0) u_sdram.mem[word_addr][7:0] = val;
|
||||||
|
else u_sdram.mem[word_addr][15:8] = val;
|
||||||
|
end
|
||||||
|
endtask
|
||||||
|
|
||||||
|
function automatic signed [7:0] peek_byte(input [ADDR_WIDTH-1:0] byte_addr);
|
||||||
|
reg [21:0] word_addr;
|
||||||
|
begin
|
||||||
|
word_addr = byte_addr[ADDR_WIDTH-1:1];
|
||||||
|
peek_byte = (byte_addr[0] == 1'b0) ? u_sdram.mem[word_addr][7:0] : u_sdram.mem[word_addr][15:8];
|
||||||
|
end
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
// ---- SPI master BFM (matches spi_host_bridge.v's own protocol,
|
||||||
|
// same realistic 500ns-bit-period convention as tb_spi_host_
|
||||||
|
// bridge.v -- see that module's header on the CDC margin reason) ----
|
||||||
|
task spi_byte(input [7:0] tx, output [7:0] rx);
|
||||||
|
integer i;
|
||||||
|
begin
|
||||||
|
rx = 8'h00;
|
||||||
|
for (i = 7; i >= 0; i = i - 1) begin
|
||||||
|
spi_mosi = tx[i];
|
||||||
|
#200; spi_sclk = 1; #50; rx = {rx[6:0], spi_miso}; #50; spi_sclk = 0; #200;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
endtask
|
||||||
|
|
||||||
|
task write_job(input [3:0] node_id, input [2:0] required, input [15:0] producer_ids,
|
||||||
|
input [22:0] x_base, input [22:0] w_base, input [15:0] n_tiles,
|
||||||
|
input [22:0] result_addr);
|
||||||
|
reg [7:0] rxb;
|
||||||
|
begin
|
||||||
|
spi_cs_n = 0; #20;
|
||||||
|
spi_byte(8'h10, rxb);
|
||||||
|
spi_byte({4'b0, node_id}, rxb);
|
||||||
|
spi_byte({5'b0, required}, rxb);
|
||||||
|
spi_byte(producer_ids[15:8], rxb);
|
||||||
|
spi_byte(producer_ids[7:0], rxb);
|
||||||
|
spi_byte({1'b0, x_base[22:16]}, rxb);
|
||||||
|
spi_byte(x_base[15:8], rxb);
|
||||||
|
spi_byte(x_base[7:0], rxb);
|
||||||
|
spi_byte({1'b0, w_base[22:16]}, rxb);
|
||||||
|
spi_byte(w_base[15:8], rxb);
|
||||||
|
spi_byte(w_base[7:0], rxb);
|
||||||
|
spi_byte(n_tiles[15:8], rxb);
|
||||||
|
spi_byte(n_tiles[7:0], rxb);
|
||||||
|
spi_byte({1'b0, result_addr[22:16]}, rxb);
|
||||||
|
spi_byte(result_addr[15:8], rxb);
|
||||||
|
spi_byte(result_addr[7:0], rxb);
|
||||||
|
// hold CS through the reg_valid/reg_ready handshake (may
|
||||||
|
// need a few extra idle clocks if the target slot is busy)
|
||||||
|
#20000;
|
||||||
|
spi_cs_n = 1; #200;
|
||||||
|
end
|
||||||
|
endtask
|
||||||
|
|
||||||
|
integer n, k, t, errors, tests;
|
||||||
|
reg signed [31:0] acc;
|
||||||
|
reg signed [7:0] golden, real_y;
|
||||||
|
localparam N_TILES = 2;
|
||||||
|
|
||||||
|
initial begin
|
||||||
|
errors = 0; tests = 0;
|
||||||
|
ext_rst_n = 0;
|
||||||
|
repeat (20) @(posedge osc_clk);
|
||||||
|
ext_rst_n = 1;
|
||||||
|
repeat (10) @(posedge osc_clk);
|
||||||
|
|
||||||
|
// preload: 2 independent single-tile (P_IN=8) neurons sharing
|
||||||
|
// one activation vector, at x_base=0x001000, weights at
|
||||||
|
// 0x002000 (neuron0) / 0x002010 (neuron1), results at 0x003000
|
||||||
|
for (k = 0; k < 8; k = k + 1) poke_byte(23'h001000 + k, k[7:0] + 1);
|
||||||
|
for (n = 0; n < 2; n = n + 1)
|
||||||
|
for (k = 0; k < 8; k = k + 1)
|
||||||
|
poke_byte(23'h002000 + n*16 + k, ((n+k) % 4) + 1);
|
||||||
|
poke_byte(23'h003000, 8'sd0);
|
||||||
|
poke_byte(23'h003001, 8'sd0);
|
||||||
|
|
||||||
|
wait (dut.u_sdram_backend.u_sdram_ctrl.state == dut.u_sdram_backend.u_sdram_ctrl.S_IDLE);
|
||||||
|
@(posedge dut.clk_sys);
|
||||||
|
|
||||||
|
write_job(4'd0, 3'd0, 16'h0000, 23'h001000, 23'h002000, 16'd1, 23'h003000);
|
||||||
|
write_job(4'd1, 3'd0, 16'h0000, 23'h001000, 23'h002010, 16'd1, 23'h003001);
|
||||||
|
|
||||||
|
// wait for both results to land (generous margin)
|
||||||
|
repeat (3000) @(posedge dut.clk_sys);
|
||||||
|
|
||||||
|
for (n = 0; n < 2; n = n + 1) begin
|
||||||
|
acc = 0;
|
||||||
|
for (t = 0; t < N_TILES/N_TILES; t = t + 1) ; // no-op, single tile
|
||||||
|
for (k = 0; k < 8; k = k + 1)
|
||||||
|
acc = acc + peek_byte(23'h001000 + k) * peek_byte(23'h002000 + n*16 + k);
|
||||||
|
golden = relu_sat(acc);
|
||||||
|
real_y = peek_byte(23'h003000 + n);
|
||||||
|
tests = tests + 1;
|
||||||
|
if (real_y !== golden) begin
|
||||||
|
errors = errors + 1;
|
||||||
|
$display("FAIL smoke neuron %0d: real=%0d golden=%0d", n, real_y, golden);
|
||||||
|
end else begin
|
||||||
|
$display("PASS smoke neuron %0d: real=%0d golden=%0d", n, real_y, golden);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
$display("=== tb_fpga_neural_v2_top_smoke: %0d/%0d PASS ===", tests-errors, tests);
|
||||||
|
if (errors != 0) $display("*** %0d FAILURES ***", errors);
|
||||||
|
$finish;
|
||||||
|
end
|
||||||
|
|
||||||
|
endmodule
|
||||||
@@ -235,6 +235,11 @@ module tb #(
|
|||||||
reg measure_en;
|
reg measure_en;
|
||||||
integer total_cycles;
|
integer total_cycles;
|
||||||
integer psram_busy_cycles;
|
integer psram_busy_cycles;
|
||||||
|
integer ni; // moved up from its original later declaration point
|
||||||
|
// (STEP20 tooling-compatibility fix, zero behavior
|
||||||
|
// change -- see nms_memory_manager_stream_wide.v's own
|
||||||
|
// header note on icarus 13.0's stricter declared-
|
||||||
|
// before-use rule for procedural blocks)
|
||||||
genvar gi;
|
genvar gi;
|
||||||
|
|
||||||
reg [N_SLOTS_CFG-1:0] slot_busy_bit; // memory_manager.state != MM_IDLE, this cycle
|
reg [N_SLOTS_CFG-1:0] slot_busy_bit; // memory_manager.state != MM_IDLE, this cycle
|
||||||
@@ -356,8 +361,8 @@ module tb #(
|
|||||||
// the weight specifically is NOT yet ready (tile_idx>=wgt_ready_count)
|
// the weight specifically is NOT yet ready (tile_idx>=wgt_ready_count)
|
||||||
// and the FSM is genuinely stalled on it (not mid-read-pipeline, not
|
// and the FSM is genuinely stalled on it (not mid-read-pipeline, not
|
||||||
// already holding a valid operand).
|
// already holding a valid operand).
|
||||||
wire [N_SLOTS_CFG-1:0] slot_could_present_act;
|
reg [N_SLOTS_CFG-1:0] slot_could_present_act;
|
||||||
wire [N_SLOTS_CFG-1:0] slot_weight_blocking;
|
reg [N_SLOTS_CFG-1:0] slot_weight_blocking;
|
||||||
reg [N_SLOTS_CFG-1:0] slot_stalled_this_tile; // sticky per current tile_idx
|
reg [N_SLOTS_CFG-1:0] slot_stalled_this_tile; // sticky per current tile_idx
|
||||||
reg [31:0] prev_tile_idx [0:N_SLOTS_CFG-1];
|
reg [31:0] prev_tile_idx [0:N_SLOTS_CFG-1];
|
||||||
integer weight_stall_cycles [0:N_SLOTS_CFG-1];
|
integer weight_stall_cycles [0:N_SLOTS_CFG-1];
|
||||||
@@ -419,7 +424,6 @@ module tb #(
|
|||||||
// Director/dependency bookkeeping
|
// Director/dependency bookkeeping
|
||||||
integer jobs_allocated, jobs_completed, wakeups;
|
integer jobs_allocated, jobs_completed, wakeups;
|
||||||
integer waiting_sum, ready_sum, dispatched_sum, sample_count;
|
integer waiting_sum, ready_sum, dispatched_sum, sample_count;
|
||||||
integer ni;
|
|
||||||
|
|
||||||
// Occupancy sampling is EXPENSIVE (a full N_NODES=512 scan) and is
|
// Occupancy sampling is EXPENSIVE (a full N_NODES=512 scan) and is
|
||||||
// only needed for the small/structural workloads (A/B/E/F), not
|
// only needed for the small/structural workloads (A/B/E/F), not
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
`timescale 1ns/1ps
|
||||||
|
|
||||||
|
// ================================================================
|
||||||
|
// FPGA-Neural V2 -- ECP5 PLL wrapper (STEP20, real clock architecture)
|
||||||
|
//
|
||||||
|
// 16 MHz board oscillator -> EHXPLLL -> 64 MHz system clock.
|
||||||
|
//
|
||||||
|
// Parameters below are the REAL, tool-generated output of Project
|
||||||
|
// Trellis's own `ecppll` utility (v1.4):
|
||||||
|
// ecppll -i 16 --clkin_name=clk_16mhz -o 64 --clkout0_name=clk_sys \
|
||||||
|
// -n ecp5_pll_16to64 -f pll_64.v
|
||||||
|
// Refclk divisor: 1, Feedback divisor: 4, clkout0 divisor: 9
|
||||||
|
// VCO frequency: 576 MHz (within the ECP5 PLL's documented
|
||||||
|
// 400-800MHz VCO range), clkout0 frequency: 64 MHz exactly
|
||||||
|
// (16 * 4 / (1*... ) -- integer, zero-error ratio).
|
||||||
|
//
|
||||||
|
// 64MHz was chosen (not 80MHz) per this step's own real, multi-seed
|
||||||
|
// P&R timing data on the FINAL board-level top (SPI host bridge +
|
||||||
|
// host-arb SDRAM port added on top of the STEP19 compute+memory
|
||||||
|
// design): see hardware/v2/docs/TIMING.md for the full seed table.
|
||||||
|
// A single lucky seed reaching into the 80s MHz range is NOT treated
|
||||||
|
// as the operating frequency -- 64MHz is the highest frequency at
|
||||||
|
// which ALL measured seeds close timing with real margin.
|
||||||
|
//
|
||||||
|
// SIMULATION: EHXPLLL has no open, licensable behavioral model (Lattice
|
||||||
|
// ships it only inside their own encrypted simulation libraries), so
|
||||||
|
// this wrapper provides a behavioral bypass under `SIM` for iverilog
|
||||||
|
// and Verilator alike -- clk_sys tracks clk_16mhz directly and
|
||||||
|
// `locked` is tied high. This is a DECLARED simulation-only stand-in,
|
||||||
|
// not a claim that PLL lock timing has been simulated; real lock
|
||||||
|
// behavior is only characterized by nextpnr-ecp5 static timing and,
|
||||||
|
// eventually, real hardware bring-up (see FIRST_POWER_ON.md).
|
||||||
|
// ================================================================
|
||||||
|
|
||||||
|
module ecp5_pll_sys_clk (
|
||||||
|
input wire clk_16mhz,
|
||||||
|
output wire clk_sys,
|
||||||
|
output wire locked
|
||||||
|
);
|
||||||
|
|
||||||
|
`ifdef SIM
|
||||||
|
|
||||||
|
assign clk_sys = clk_16mhz;
|
||||||
|
assign locked = 1'b1;
|
||||||
|
|
||||||
|
`else
|
||||||
|
|
||||||
|
(* FREQUENCY_PIN_CLKI="16" *)
|
||||||
|
(* FREQUENCY_PIN_CLKOP="64" *)
|
||||||
|
(* ICP_CURRENT="12" *) (* LPF_RESISTOR="8" *) (* MFG_ENABLE_FILTEROPAMP="1" *) (* MFG_GMCREF_SEL="2" *)
|
||||||
|
EHXPLLL #(
|
||||||
|
.PLLRST_ENA("DISABLED"),
|
||||||
|
.INTFB_WAKE("DISABLED"),
|
||||||
|
.STDBY_ENABLE("DISABLED"),
|
||||||
|
.DPHASE_SOURCE("DISABLED"),
|
||||||
|
.OUTDIVIDER_MUXA("DIVA"),
|
||||||
|
.OUTDIVIDER_MUXB("DIVB"),
|
||||||
|
.OUTDIVIDER_MUXC("DIVC"),
|
||||||
|
.OUTDIVIDER_MUXD("DIVD"),
|
||||||
|
.CLKI_DIV(1),
|
||||||
|
.CLKOP_ENABLE("ENABLED"),
|
||||||
|
.CLKOP_DIV(9),
|
||||||
|
.CLKOP_CPHASE(4),
|
||||||
|
.CLKOP_FPHASE(0),
|
||||||
|
.FEEDBK_PATH("CLKOP"),
|
||||||
|
.CLKFB_DIV(4)
|
||||||
|
) pll_i (
|
||||||
|
.RST(1'b0),
|
||||||
|
.STDBY(1'b0),
|
||||||
|
.CLKI(clk_16mhz),
|
||||||
|
.CLKOP(clk_sys),
|
||||||
|
.CLKFB(clk_sys),
|
||||||
|
.CLKINTFB(),
|
||||||
|
.PHASESEL0(1'b0),
|
||||||
|
.PHASESEL1(1'b0),
|
||||||
|
.PHASEDIR(1'b1),
|
||||||
|
.PHASESTEP(1'b1),
|
||||||
|
.PHASELOADREG(1'b1),
|
||||||
|
.PLLWAKESYNC(1'b0),
|
||||||
|
.ENCLKOP(1'b0),
|
||||||
|
.LOCK(locked)
|
||||||
|
);
|
||||||
|
|
||||||
|
`endif
|
||||||
|
|
||||||
|
endmodule
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
`timescale 1ns/1ps
|
||||||
|
|
||||||
|
// ================================================================
|
||||||
|
// FPGA-Neural V2 -- reset synchronizer (STEP20, real reset/POR path)
|
||||||
|
//
|
||||||
|
// Standard async-assert / sync-deassert double-flop reset bridge.
|
||||||
|
// Asserts `rst` IMMEDIATELY (combinationally) when either the
|
||||||
|
// external POR/supervisor (ext_rst_n, active-low) is asserted OR the
|
||||||
|
// PLL has not yet reported LOCK -- both real, physical conditions
|
||||||
|
// under which no downstream logic (SDRAM controller, dependency
|
||||||
|
// manager, SPI bridge) may be considered valid. Deassertion is
|
||||||
|
// synchronized to `clk_sys` through two flip-flops so no downstream
|
||||||
|
// flop ever sees an asynchronous release edge.
|
||||||
|
// ================================================================
|
||||||
|
|
||||||
|
module reset_sync (
|
||||||
|
input wire clk_sys,
|
||||||
|
input wire ext_rst_n, // external POR/supervisor, active-low
|
||||||
|
input wire pll_locked,
|
||||||
|
output wire rst // synchronous-deassert, active-high
|
||||||
|
);
|
||||||
|
|
||||||
|
wire async_rst_n = ext_rst_n & pll_locked;
|
||||||
|
|
||||||
|
reg [1:0] sync_ff;
|
||||||
|
|
||||||
|
always @(posedge clk_sys or negedge async_rst_n) begin
|
||||||
|
if (!async_rst_n) sync_ff <= 2'b00;
|
||||||
|
else sync_ff <= {sync_ff[0], 1'b1};
|
||||||
|
end
|
||||||
|
|
||||||
|
assign rst = ~sync_ff[1];
|
||||||
|
|
||||||
|
endmodule
|
||||||
@@ -0,0 +1,435 @@
|
|||||||
|
`timescale 1ns/1ps
|
||||||
|
|
||||||
|
// ================================================================
|
||||||
|
// FPGA-Neural V2 -- SPI HOST BRIDGE (STEP20, physical host interface)
|
||||||
|
//
|
||||||
|
// Replaces the 110-pin reg_*/testbench-only bus as the PHYSICAL board
|
||||||
|
// interface. The internal reg_*/mem_* ports below are UNCHANGED in
|
||||||
|
// shape/semantics from the ones nms_dataflow_core_sdram.v and
|
||||||
|
// sdram_unified_backend.v's AR port already expose -- this module is
|
||||||
|
// a pure protocol translator (SPI bytes -> the same internal signals
|
||||||
|
// simulation already drives directly), so nms_dataflow_core_sdram.v,
|
||||||
|
// dependency_manager.v, neural_processor.v and sdram_unified_backend.v
|
||||||
|
// remain byte-for-byte unchanged (STEP19/STEP20 standing constraint).
|
||||||
|
//
|
||||||
|
// Physical layer (byte shift register + CS framing + CDC synchronizers)
|
||||||
|
// re-derives the same proven design as hardware/v1/rtl/spi_slave.v
|
||||||
|
// (SPI mode 0, MSB-first, one opcode per CS-low period, double-flop
|
||||||
|
// CDC on sclk/mosi/cs_n) -- reimplemented here as a NEW, independently
|
||||||
|
// owned V2 file so V2 continues to instantiate ZERO V1 RTL (STEP19's
|
||||||
|
// own "zero V1 files in the V2 compile list" property is preserved).
|
||||||
|
//
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
// PROTOCOL (new, V2-specific -- one opcode byte, MSB-first, per
|
||||||
|
// CS-low transaction; multi-byte fields are MSB-first):
|
||||||
|
//
|
||||||
|
// 0x00 NOP -- 0 payload bytes.
|
||||||
|
// 0x0F RESET -- 0 payload bytes. Pulses soft_rst_pulse for
|
||||||
|
// one clk cycle after CS rises.
|
||||||
|
// 0x10 WRITE_JOB -- 15 payload bytes, registers one dependency-
|
||||||
|
// manager job (== one reg_valid/reg_* handshake):
|
||||||
|
// byte0 = {4'b0,node_id[3:0]}
|
||||||
|
// byte1 = {5'b0,required[2:0]}
|
||||||
|
// byte2:3 = producer_ids[15:0]
|
||||||
|
// byte4:6 = x_base[22:0] (byte4 msb={1'b0,x_base[22:16]})
|
||||||
|
// byte7:9 = w_base[22:0]
|
||||||
|
// byte10:11= n_tiles[15:0]
|
||||||
|
// byte12:14= result_addr[22:0]
|
||||||
|
// reg_valid is asserted and HELD until the
|
||||||
|
// cycle reg_ready also reads 1 (same-cycle
|
||||||
|
// valid&&ready acceptance, matching
|
||||||
|
// dependency_manager.v's own combinational
|
||||||
|
// reg_ready contract) -- never a blind pulse.
|
||||||
|
// 0x20 STATUS -- 0 payload bytes. Returns 1 byte on MISO
|
||||||
|
// (clocked out during payload byte 1):
|
||||||
|
// bit0 = job_busy (WRITE_JOB waiting on reg_ready)
|
||||||
|
// bit1 = mem_busy (WRITE_MEM/READ_MEM waiting on mem_ready)
|
||||||
|
// bit2 = last_job_accepted (sticky, cleared by next WRITE_JOB)
|
||||||
|
// bits[7:3] = 0 (reserved)
|
||||||
|
// 0x01 WRITE_MEM -- 5 header bytes + 2*len_words payload bytes:
|
||||||
|
// byte0:2 = addr[22:0] (WORD address, matches
|
||||||
|
// sdram_unified_backend's AR port
|
||||||
|
// convention -- NOT a byte address)
|
||||||
|
// byte3:4 = len_words[15:0] (number of 16-bit
|
||||||
|
// words to write, len_words>=1)
|
||||||
|
// then len_words * 2 bytes of data, MSB-first
|
||||||
|
// per word; each word is written via one
|
||||||
|
// mem_req/mem_ready handshake (lb_n=ub_n=0,
|
||||||
|
// full 16-bit write) before the next word's
|
||||||
|
// bytes are accepted.
|
||||||
|
// 0x02 READ_MEM -- 5 header bytes (addr + len_words, same shape
|
||||||
|
// as WRITE_MEM), 0 further MOSI payload; the
|
||||||
|
// 2*len_words response bytes are clocked out
|
||||||
|
// on MISO starting at payload byte 6, MSB-
|
||||||
|
// first per word, one mem_req/mem_ready
|
||||||
|
// read per word.
|
||||||
|
//
|
||||||
|
// Any opcode byte not listed above is treated as NOP (0 payload,
|
||||||
|
// MISO drives 0x00) -- matches spi_engine.v's own "unknown opcode is
|
||||||
|
// inert, never wedges the bus" precedent.
|
||||||
|
// ================================================================
|
||||||
|
|
||||||
|
module spi_host_bridge #(
|
||||||
|
parameter ADDR_WIDTH = 23,
|
||||||
|
parameter N_NODES = 16,
|
||||||
|
parameter MAX_DEPS = 4
|
||||||
|
)(
|
||||||
|
input wire clk,
|
||||||
|
input wire rst,
|
||||||
|
|
||||||
|
// ---- physical SPI pins ----
|
||||||
|
input wire sclk,
|
||||||
|
input wire mosi,
|
||||||
|
output wire miso,
|
||||||
|
input wire cs_n,
|
||||||
|
|
||||||
|
// ---- job registration (-> nms_dataflow_core_sdram.v) ----
|
||||||
|
output reg reg_valid,
|
||||||
|
input wire reg_ready,
|
||||||
|
output reg [$clog2(N_NODES)-1:0] reg_node_id,
|
||||||
|
output reg [$clog2(MAX_DEPS+1)-1:0] reg_required,
|
||||||
|
output reg [MAX_DEPS*$clog2(N_NODES)-1:0] reg_producer_ids,
|
||||||
|
output reg [ADDR_WIDTH-1:0] reg_x_base,
|
||||||
|
output reg [ADDR_WIDTH-1:0] reg_w_base,
|
||||||
|
output reg [15:0] reg_n_tiles,
|
||||||
|
output reg [ADDR_WIDTH-1:0] reg_result_addr,
|
||||||
|
|
||||||
|
// ---- host raw SDRAM access (-> host-arb slot_mem_arbiter port) ----
|
||||||
|
output reg mem_req,
|
||||||
|
output reg mem_wr,
|
||||||
|
output reg [ADDR_WIDTH-1:0] mem_addr,
|
||||||
|
output reg [15:0] mem_wdata,
|
||||||
|
output reg mem_lb_n,
|
||||||
|
output reg mem_ub_n,
|
||||||
|
input wire [15:0] mem_rdata,
|
||||||
|
input wire mem_ready,
|
||||||
|
|
||||||
|
output reg soft_rst_pulse
|
||||||
|
);
|
||||||
|
|
||||||
|
localparam NODEW = $clog2(N_NODES);
|
||||||
|
localparam REQW = $clog2(MAX_DEPS+1);
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// SPI PHYSICAL LAYER (byte shift register + CS framing + CDC)
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
reg [2:0] sclk_sync, mosi_sync, cs_n_sync;
|
||||||
|
always @(posedge clk) begin
|
||||||
|
if (rst) begin
|
||||||
|
sclk_sync <= 3'b000; mosi_sync <= 3'b000; cs_n_sync <= 3'b111;
|
||||||
|
end else begin
|
||||||
|
sclk_sync <= {sclk_sync[1:0], sclk};
|
||||||
|
mosi_sync <= {mosi_sync[1:0], mosi};
|
||||||
|
cs_n_sync <= {cs_n_sync[1:0], cs_n};
|
||||||
|
end
|
||||||
|
end
|
||||||
|
wire sclk_s = sclk_sync[2];
|
||||||
|
wire cs_n_s = cs_n_sync[2];
|
||||||
|
wire mosi_s = mosi_sync[2];
|
||||||
|
|
||||||
|
reg sclk_prev, cs_n_prev;
|
||||||
|
always @(posedge clk) begin
|
||||||
|
if (rst) begin sclk_prev <= 1'b0; cs_n_prev <= 1'b1; end
|
||||||
|
else begin sclk_prev <= sclk_s; cs_n_prev <= cs_n_s; end
|
||||||
|
end
|
||||||
|
wire sclk_rise = sclk_s & ~sclk_prev;
|
||||||
|
wire cs_fell = ~cs_n_s & cs_n_prev;
|
||||||
|
wire cs_rose = cs_n_s & ~cs_n_prev;
|
||||||
|
wire cs_active = ~cs_n_s;
|
||||||
|
|
||||||
|
reg [2:0] bit_count;
|
||||||
|
reg [7:0] rx_shift;
|
||||||
|
reg [7:0] rx_byte;
|
||||||
|
reg rx_valid;
|
||||||
|
|
||||||
|
// tx_byte is driven COMBINATIONALLY by the protocol FSM below (see
|
||||||
|
// tx_mux) -- always reflects "the byte MISO should show next".
|
||||||
|
//
|
||||||
|
// IMPORTANT (found via this module's own isolated regression,
|
||||||
|
// STEP20 -- two successive real bugs before this final design):
|
||||||
|
//
|
||||||
|
// Draft 1 used a conventional per-bit INCREMENTAL shift register
|
||||||
|
// for MISO (load tx_byte once at a byte boundary, then shift one
|
||||||
|
// position per falling edge, mirroring hardware/v1/rtl/
|
||||||
|
// spi_slave.v's own proven convention). It failed because
|
||||||
|
// `bit_count` (incremented on the RISING-edge detector) is ALWAYS
|
||||||
|
// already one bit ahead of what the FALLING-edge detector sees for
|
||||||
|
// that SAME physical bit -- a rising edge is always detected
|
||||||
|
// before that bit's own falling edge, since both go through the
|
||||||
|
// same CDC latency but the physical fall itself comes later in
|
||||||
|
// time. So "prepare tx_shift for bit_count+1" at a falling edge
|
||||||
|
// that already observes the incremented bit_count silently skips
|
||||||
|
// a bit position, corrupting the byte by one place (root-caused
|
||||||
|
// via this module's own tb_spi_host_bridge.v with a full internal-
|
||||||
|
// signal trace, not by inspection).
|
||||||
|
//
|
||||||
|
// Draft 2 tried removing the shift register entirely (index
|
||||||
|
// tx_byte directly by bit_count on EVERY bit, driven purely
|
||||||
|
// combinationally). That failed a different way: sampling MISO
|
||||||
|
// even slightly after the CDC latency that follows a bit's own
|
||||||
|
// rising edge (normal SPI master behavior, not a torture case)
|
||||||
|
// already sees bit_count having advanced to the NEXT index.
|
||||||
|
//
|
||||||
|
// Both drafts share one fact once it's made explicit: at the
|
||||||
|
// moment ANY falling edge is internally detected, `bit_count`
|
||||||
|
// ALREADY equals the index of the bit that is about to be
|
||||||
|
// sampled next (not the bit whose fall just fired). The fix below
|
||||||
|
// uses exactly that fact instead of fighting it: on every detected
|
||||||
|
// falling edge, load `miso_shift_bit` directly from
|
||||||
|
// tx_byte[7-bit_count] (no incremental shift, no off-by-one).
|
||||||
|
// Between falling edges -- including an extended SCLK-idle wait,
|
||||||
|
// a real, INTENDED use of this protocol for READ_MEM/mem_req
|
||||||
|
// latency (see module header) -- `bit_count==0` is additionally
|
||||||
|
// driven live/combinationally so a response that only becomes
|
||||||
|
// known DURING the idle wait (no falling edge occurs to refresh
|
||||||
|
// it) is still correct once the master resumes clocking.
|
||||||
|
wire [7:0] tx_byte;
|
||||||
|
reg miso_shift_bit;
|
||||||
|
|
||||||
|
assign miso = (cs_active && bit_count == 3'd0) ? tx_byte[7] : miso_shift_bit;
|
||||||
|
|
||||||
|
always @(posedge clk) begin
|
||||||
|
if (rst) begin
|
||||||
|
bit_count <= 3'd0; rx_shift <= 8'h00; rx_byte <= 8'h00; rx_valid <= 1'b0;
|
||||||
|
miso_shift_bit <= 1'b0;
|
||||||
|
end else begin
|
||||||
|
rx_valid <= 1'b0;
|
||||||
|
if (cs_fell) begin
|
||||||
|
bit_count <= 3'd0;
|
||||||
|
end else if (cs_active) begin
|
||||||
|
if (sclk_rise) begin
|
||||||
|
rx_shift <= {rx_shift[6:0], mosi_s};
|
||||||
|
if (bit_count == 3'd7) begin
|
||||||
|
bit_count <= 3'd0;
|
||||||
|
rx_byte <= {rx_shift[6:0], mosi_s};
|
||||||
|
rx_valid <= 1'b1;
|
||||||
|
end else begin
|
||||||
|
bit_count <= bit_count + 3'd1;
|
||||||
|
end
|
||||||
|
end else if (~sclk_s & sclk_prev) begin // sclk_fall
|
||||||
|
miso_shift_bit <= tx_byte[3'd7 - bit_count];
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// PROTOCOL FSM
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
localparam OP_NOP = 8'h00;
|
||||||
|
localparam OP_WRITE_MEM = 8'h01;
|
||||||
|
localparam OP_READ_MEM = 8'h02;
|
||||||
|
localparam OP_RESET = 8'h0F;
|
||||||
|
localparam OP_WRITE_JOB = 8'h10;
|
||||||
|
localparam OP_STATUS = 8'h20;
|
||||||
|
|
||||||
|
localparam ST_OPCODE = 4'd0;
|
||||||
|
localparam ST_JOB = 4'd1; // collecting 15 WRITE_JOB payload bytes
|
||||||
|
localparam ST_JOB_WAIT= 4'd2; // reg_valid held, waiting reg_ready
|
||||||
|
localparam ST_MEM_ADDR= 4'd3; // collecting 3 addr bytes
|
||||||
|
localparam ST_MEM_LEN = 4'd4; // collecting 2 length bytes
|
||||||
|
localparam ST_MEM_WD = 4'd5; // WRITE_MEM: collecting 2 data bytes/word
|
||||||
|
localparam ST_MEM_WISS= 4'd6; // WRITE_MEM: issue+wait mem_req
|
||||||
|
localparam ST_MEM_RISS= 4'd7; // READ_MEM: issue+wait mem_req
|
||||||
|
localparam ST_MEM_ROUT= 4'd8; // READ_MEM: shifting the 2 bytes of a word out
|
||||||
|
localparam ST_IGNORE = 4'd9; // opcode consumed / unknown, wait for cs_rose
|
||||||
|
|
||||||
|
reg [3:0] state;
|
||||||
|
reg [7:0] opcode;
|
||||||
|
reg [3:0] byte_idx; // generic byte counter within a field
|
||||||
|
reg [15:0] len_words;
|
||||||
|
reg [15:0] word_cnt;
|
||||||
|
reg [15:0] cur_word; // WRITE_MEM: assembling MSB,LSB; READ_MEM: holding readback
|
||||||
|
reg job_busy_r, mem_busy_r, last_job_accepted_r;
|
||||||
|
|
||||||
|
// combinational tx byte mux -- STATUS response, READ_MEM data,
|
||||||
|
// everything else drives 0x00
|
||||||
|
reg [7:0] tx_mux;
|
||||||
|
always @(*) begin
|
||||||
|
tx_mux = 8'h00;
|
||||||
|
if (opcode == OP_STATUS)
|
||||||
|
tx_mux = {5'b0, last_job_accepted_r, mem_busy_r, job_busy_r};
|
||||||
|
else if (opcode == OP_READ_MEM && state == ST_MEM_ROUT)
|
||||||
|
tx_mux = (byte_idx == 4'd0) ? cur_word[15:8] : cur_word[7:0];
|
||||||
|
end
|
||||||
|
assign tx_byte = tx_mux;
|
||||||
|
|
||||||
|
always @(posedge clk) begin
|
||||||
|
if (rst) begin
|
||||||
|
state <= ST_OPCODE; opcode <= 8'h00; byte_idx <= 4'd0;
|
||||||
|
len_words <= 16'd0; word_cnt <= 16'd0; cur_word <= 16'd0;
|
||||||
|
reg_valid <= 1'b0; reg_node_id <= {NODEW{1'b0}}; reg_required <= {REQW{1'b0}};
|
||||||
|
reg_producer_ids <= {(MAX_DEPS*NODEW){1'b0}};
|
||||||
|
reg_x_base <= {ADDR_WIDTH{1'b0}}; reg_w_base <= {ADDR_WIDTH{1'b0}};
|
||||||
|
reg_n_tiles <= 16'd0; reg_result_addr <= {ADDR_WIDTH{1'b0}};
|
||||||
|
mem_req <= 1'b0; mem_wr <= 1'b0; mem_addr <= {ADDR_WIDTH{1'b0}};
|
||||||
|
mem_wdata <= 16'd0; mem_lb_n <= 1'b0; mem_ub_n <= 1'b0;
|
||||||
|
soft_rst_pulse <= 1'b0;
|
||||||
|
job_busy_r <= 1'b0; mem_busy_r <= 1'b0; last_job_accepted_r <= 1'b0;
|
||||||
|
end else begin
|
||||||
|
mem_req <= 1'b0;
|
||||||
|
soft_rst_pulse <= 1'b0;
|
||||||
|
|
||||||
|
// A new CS assertion normally starts a fresh opcode byte.
|
||||||
|
// EXCEPTION (found via this module's own board-level
|
||||||
|
// integration smoke test, STEP20): if the PREVIOUS
|
||||||
|
// transaction is still pending a backend handshake
|
||||||
|
// (ST_JOB_WAIT/ST_MEM_WISS/ST_MEM_RISS -- e.g. reg_valid
|
||||||
|
// held, waiting on dependency_manager's reg_ready, per
|
||||||
|
// this module's own documented "hold until accepted"
|
||||||
|
// contract), do NOT reset state/byte_idx here: a naive
|
||||||
|
// unconditional reset lets a new WRITE_JOB's incoming
|
||||||
|
// bytes start overwriting reg_node_id/reg_x_base/reg_
|
||||||
|
// w_base/etc THROUGH THE SAME REGISTERS while the OLD
|
||||||
|
// job's reg_valid is still asserted and not yet accepted,
|
||||||
|
// corrupting the first job's dispatch with a mix of both
|
||||||
|
// jobs' fields (confirmed: two back-to-back WRITE_JOB
|
||||||
|
// transactions produced swapped/wrong result values,
|
||||||
|
// root-caused via a full internal signal trace before
|
||||||
|
// this fix). Mirrors the same protection already applied
|
||||||
|
// to cs_rose below.
|
||||||
|
if (cs_fell && state != ST_JOB_WAIT && state != ST_MEM_WISS && state != ST_MEM_RISS) begin
|
||||||
|
state <= ST_OPCODE;
|
||||||
|
byte_idx <= 4'd0;
|
||||||
|
end else if (!cs_fell && rx_valid) begin
|
||||||
|
case (state)
|
||||||
|
ST_OPCODE: begin
|
||||||
|
opcode <= rx_byte;
|
||||||
|
byte_idx <= 4'd0;
|
||||||
|
case (rx_byte)
|
||||||
|
OP_WRITE_JOB: state <= ST_JOB;
|
||||||
|
OP_WRITE_MEM: state <= ST_MEM_ADDR;
|
||||||
|
OP_READ_MEM: state <= ST_MEM_ADDR;
|
||||||
|
OP_RESET: state <= ST_IGNORE;
|
||||||
|
default: state <= ST_IGNORE; // NOP, STATUS: no MOSI payload
|
||||||
|
endcase
|
||||||
|
end
|
||||||
|
|
||||||
|
ST_JOB: begin
|
||||||
|
case (byte_idx)
|
||||||
|
4'd0: reg_node_id <= rx_byte[NODEW-1:0];
|
||||||
|
4'd1: reg_required <= rx_byte[REQW-1:0];
|
||||||
|
4'd2: reg_producer_ids[15:8] <= rx_byte;
|
||||||
|
4'd3: reg_producer_ids[7:0] <= rx_byte;
|
||||||
|
4'd4: reg_x_base[22:16] <= rx_byte[6:0];
|
||||||
|
4'd5: reg_x_base[15:8] <= rx_byte;
|
||||||
|
4'd6: reg_x_base[7:0] <= rx_byte;
|
||||||
|
4'd7: reg_w_base[22:16] <= rx_byte[6:0];
|
||||||
|
4'd8: reg_w_base[15:8] <= rx_byte;
|
||||||
|
4'd9: reg_w_base[7:0] <= rx_byte;
|
||||||
|
4'd10: reg_n_tiles[15:8] <= rx_byte;
|
||||||
|
4'd11: reg_n_tiles[7:0] <= rx_byte;
|
||||||
|
4'd12: reg_result_addr[22:16] <= rx_byte[6:0];
|
||||||
|
4'd13: reg_result_addr[15:8] <= rx_byte;
|
||||||
|
4'd14: begin
|
||||||
|
reg_result_addr[7:0] <= rx_byte;
|
||||||
|
reg_valid <= 1'b1;
|
||||||
|
last_job_accepted_r <= 1'b0;
|
||||||
|
state <= ST_JOB_WAIT;
|
||||||
|
end
|
||||||
|
endcase
|
||||||
|
if (byte_idx != 4'd14) byte_idx <= byte_idx + 4'd1;
|
||||||
|
end
|
||||||
|
|
||||||
|
ST_MEM_ADDR: begin
|
||||||
|
case (byte_idx)
|
||||||
|
4'd0: mem_addr[22:16] <= rx_byte[6:0];
|
||||||
|
4'd1: mem_addr[15:8] <= rx_byte;
|
||||||
|
4'd2: begin
|
||||||
|
mem_addr[7:0] <= rx_byte;
|
||||||
|
state <= ST_MEM_LEN;
|
||||||
|
end
|
||||||
|
endcase
|
||||||
|
if (byte_idx != 4'd2) byte_idx <= byte_idx + 4'd1;
|
||||||
|
else byte_idx <= 4'd0;
|
||||||
|
end
|
||||||
|
|
||||||
|
ST_MEM_LEN: begin
|
||||||
|
if (byte_idx == 4'd0) begin
|
||||||
|
len_words[15:8] <= rx_byte;
|
||||||
|
byte_idx <= 4'd1;
|
||||||
|
end else begin
|
||||||
|
len_words[7:0] <= rx_byte;
|
||||||
|
word_cnt <= {len_words[15:8], rx_byte};
|
||||||
|
byte_idx <= 4'd0;
|
||||||
|
state <= (opcode == OP_WRITE_MEM) ? ST_MEM_WD : ST_MEM_RISS;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
ST_MEM_WD: begin
|
||||||
|
if (byte_idx == 4'd0) begin
|
||||||
|
cur_word[15:8] <= rx_byte;
|
||||||
|
byte_idx <= 4'd1;
|
||||||
|
end else begin
|
||||||
|
cur_word[7:0] <= rx_byte;
|
||||||
|
state <= ST_MEM_WISS;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
default: ; // ST_JOB_WAIT/ST_MEM_WISS/ST_MEM_RISS/ST_MEM_ROUT/ST_IGNORE: no MOSI payload expected
|
||||||
|
endcase
|
||||||
|
end
|
||||||
|
|
||||||
|
// ---- non-rx_valid-driven transitions ----
|
||||||
|
if (state == ST_JOB_WAIT && reg_valid && reg_ready) begin
|
||||||
|
reg_valid <= 1'b0;
|
||||||
|
last_job_accepted_r <= 1'b1;
|
||||||
|
state <= ST_IGNORE;
|
||||||
|
end
|
||||||
|
|
||||||
|
if (state == ST_MEM_WISS && !mem_req && !mem_busy_r) begin
|
||||||
|
mem_req <= 1'b1;
|
||||||
|
mem_wr <= 1'b1;
|
||||||
|
mem_wdata <= cur_word;
|
||||||
|
mem_lb_n <= 1'b0;
|
||||||
|
mem_ub_n <= 1'b0;
|
||||||
|
mem_busy_r <= 1'b1;
|
||||||
|
end else if (state == ST_MEM_WISS && mem_busy_r && mem_ready) begin
|
||||||
|
mem_busy_r <= 1'b0;
|
||||||
|
mem_addr <= mem_addr + 1'b1;
|
||||||
|
word_cnt <= word_cnt - 1'b1;
|
||||||
|
byte_idx <= 4'd0;
|
||||||
|
state <= (word_cnt == 16'd1) ? ST_IGNORE : ST_MEM_WD;
|
||||||
|
end
|
||||||
|
|
||||||
|
if (state == ST_MEM_RISS && !mem_req && !mem_busy_r) begin
|
||||||
|
mem_req <= 1'b1;
|
||||||
|
mem_wr <= 1'b0;
|
||||||
|
mem_lb_n <= 1'b0;
|
||||||
|
mem_ub_n <= 1'b0;
|
||||||
|
mem_busy_r <= 1'b1;
|
||||||
|
end else if (state == ST_MEM_RISS && mem_busy_r && mem_ready) begin
|
||||||
|
mem_busy_r <= 1'b0;
|
||||||
|
cur_word <= mem_rdata;
|
||||||
|
byte_idx <= 4'd0;
|
||||||
|
state <= ST_MEM_ROUT;
|
||||||
|
end
|
||||||
|
if (state == ST_MEM_ROUT && rx_valid) begin
|
||||||
|
// a byte was clocked out while this state was active;
|
||||||
|
// rx_valid pulses once per real byte transferred, so
|
||||||
|
// it is also the correct "advance" event for MISO-side
|
||||||
|
// bookkeeping (mirrors spi_slave's own documented
|
||||||
|
// rx_valid-drives-advancement convention).
|
||||||
|
if (byte_idx == 4'd0) begin
|
||||||
|
byte_idx <= 4'd1;
|
||||||
|
end else begin
|
||||||
|
mem_addr <= mem_addr + 1'b1;
|
||||||
|
word_cnt <= word_cnt - 1'b1;
|
||||||
|
byte_idx <= 4'd0;
|
||||||
|
state <= (word_cnt == 16'd1) ? ST_IGNORE : ST_MEM_RISS;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
job_busy_r <= (state == ST_JOB_WAIT);
|
||||||
|
|
||||||
|
if (cs_rose) begin
|
||||||
|
if (opcode == OP_RESET) soft_rst_pulse <= 1'b1;
|
||||||
|
if (state != ST_JOB_WAIT && state != ST_MEM_WISS && state != ST_MEM_RISS)
|
||||||
|
state <= ST_OPCODE;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
endmodule
|
||||||
@@ -0,0 +1,224 @@
|
|||||||
|
`timescale 1ns/1ps
|
||||||
|
|
||||||
|
// ================================================================
|
||||||
|
// Isolated unit regression for spi_host_bridge.v (STEP20).
|
||||||
|
//
|
||||||
|
// Emulates: (1) dependency_manager.v's reg_ready contract (a level,
|
||||||
|
// asserted only when the target node is free -- here deliberately
|
||||||
|
// delayed for a few cycles on the first job to prove reg_valid is
|
||||||
|
// HELD, not pulsed blind); (2) the host-arb slot_mem_arbiter's
|
||||||
|
// mem_ready contract (one clean req/ready handshake, SDRAM-like fixed
|
||||||
|
// latency, backed by a simple associative model array standing in for
|
||||||
|
// real SDRAM content).
|
||||||
|
//
|
||||||
|
// Per spi_host_bridge.v's own documented protocol: CS must stay
|
||||||
|
// asserted (low) for the WHOLE WRITE_MEM/READ_MEM transaction,
|
||||||
|
// including the internal wait for mem_ready -- SCLK may be idled
|
||||||
|
// (held low, no toggling) during that wait without losing state. This
|
||||||
|
// testbench's SPI master BFM does exactly that.
|
||||||
|
// ================================================================
|
||||||
|
|
||||||
|
module tb_spi_host_bridge;
|
||||||
|
|
||||||
|
localparam ADDR_WIDTH = 23;
|
||||||
|
localparam N_NODES = 16;
|
||||||
|
localparam MAX_DEPS = 4;
|
||||||
|
localparam NODEW = $clog2(N_NODES);
|
||||||
|
localparam REQW = $clog2(MAX_DEPS+1);
|
||||||
|
|
||||||
|
reg clk = 0, rst = 1;
|
||||||
|
always #5 clk = ~clk; // 100MHz sim clock (arbitrary, faster than SPI)
|
||||||
|
|
||||||
|
reg sclk = 0, mosi = 0, cs_n = 1;
|
||||||
|
wire miso;
|
||||||
|
|
||||||
|
reg reg_ready_model = 0;
|
||||||
|
wire reg_valid;
|
||||||
|
wire [NODEW-1:0] reg_node_id;
|
||||||
|
wire [REQW-1:0] reg_required;
|
||||||
|
wire [MAX_DEPS*NODEW-1:0] reg_producer_ids;
|
||||||
|
wire [ADDR_WIDTH-1:0] reg_x_base, reg_w_base, reg_result_addr;
|
||||||
|
wire [15:0] reg_n_tiles;
|
||||||
|
|
||||||
|
wire mem_req, mem_wr, mem_lb_n, mem_ub_n;
|
||||||
|
wire [ADDR_WIDTH-1:0] mem_addr;
|
||||||
|
wire [15:0] mem_wdata;
|
||||||
|
reg [15:0] mem_rdata_model;
|
||||||
|
reg mem_ready_model = 0;
|
||||||
|
|
||||||
|
wire soft_rst_pulse;
|
||||||
|
|
||||||
|
spi_host_bridge #(
|
||||||
|
.ADDR_WIDTH(ADDR_WIDTH), .N_NODES(N_NODES), .MAX_DEPS(MAX_DEPS)
|
||||||
|
) dut (
|
||||||
|
.clk(clk), .rst(rst),
|
||||||
|
.sclk(sclk), .mosi(mosi), .miso(miso), .cs_n(cs_n),
|
||||||
|
.reg_valid(reg_valid), .reg_ready(reg_ready_model),
|
||||||
|
.reg_node_id(reg_node_id), .reg_required(reg_required),
|
||||||
|
.reg_producer_ids(reg_producer_ids),
|
||||||
|
.reg_x_base(reg_x_base), .reg_w_base(reg_w_base),
|
||||||
|
.reg_n_tiles(reg_n_tiles), .reg_result_addr(reg_result_addr),
|
||||||
|
.mem_req(mem_req), .mem_wr(mem_wr), .mem_addr(mem_addr),
|
||||||
|
.mem_wdata(mem_wdata), .mem_lb_n(mem_lb_n), .mem_ub_n(mem_ub_n),
|
||||||
|
.mem_rdata(mem_rdata_model), .mem_ready(mem_ready_model),
|
||||||
|
.soft_rst_pulse(soft_rst_pulse)
|
||||||
|
);
|
||||||
|
|
||||||
|
// ---- simple backing memory model: fixed 6-cycle mem_ready latency ----
|
||||||
|
reg [15:0] mem_model [0:1023];
|
||||||
|
integer mem_latency_cnt;
|
||||||
|
reg mem_pending;
|
||||||
|
always @(posedge clk) begin
|
||||||
|
if (rst) begin
|
||||||
|
mem_ready_model <= 1'b0; mem_pending <= 1'b0; mem_latency_cnt <= 0;
|
||||||
|
end else begin
|
||||||
|
mem_ready_model <= 1'b0;
|
||||||
|
if (mem_req && !mem_pending) begin
|
||||||
|
mem_pending <= 1'b1;
|
||||||
|
mem_latency_cnt <= 6;
|
||||||
|
end else if (mem_pending) begin
|
||||||
|
if (mem_latency_cnt == 0) begin
|
||||||
|
mem_pending <= 1'b0;
|
||||||
|
mem_ready_model <= 1'b1;
|
||||||
|
if (mem_wr) mem_model[mem_addr[9:0]] <= mem_wdata;
|
||||||
|
else mem_rdata_model <= mem_model[mem_addr[9:0]];
|
||||||
|
end else begin
|
||||||
|
mem_latency_cnt <= mem_latency_cnt - 1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
// ---- SPI master BFM: mode 0, MSB-first ----
|
||||||
|
// Bit period = 500ns (2MHz SPI clock) against a 100MHz sim `clk`:
|
||||||
|
// a 50x margin over the ~4-clk-cycle CDC synchronizer latency,
|
||||||
|
// representative of a REAL deployment (system clock 64-80MHz vs a
|
||||||
|
// practical SPI clock in the low single-digit MHz -- see this
|
||||||
|
// module's own header for the documented minimum ratio). A torture
|
||||||
|
// rate close to the CDC latency (as an earlier draft of this
|
||||||
|
// testbench used) is not a realistic operating point and is not
|
||||||
|
// what this module is specified against.
|
||||||
|
task spi_byte(input [7:0] tx, output [7:0] rx);
|
||||||
|
integer i;
|
||||||
|
begin
|
||||||
|
rx = 8'h00;
|
||||||
|
for (i = 7; i >= 0; i = i - 1) begin
|
||||||
|
mosi = tx[i];
|
||||||
|
#200; sclk = 1; #50; rx = {rx[6:0], miso}; #50; sclk = 0; #200;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
endtask
|
||||||
|
|
||||||
|
integer errors = 0, tests = 0;
|
||||||
|
task check(input cond, input [255:0] name);
|
||||||
|
begin
|
||||||
|
tests = tests + 1;
|
||||||
|
if (!cond) begin errors = errors + 1; $display("FAIL: %0s", name); end
|
||||||
|
else $display("PASS: %0s", name);
|
||||||
|
end
|
||||||
|
endtask
|
||||||
|
|
||||||
|
reg [7:0] rxb;
|
||||||
|
reg [ADDR_WIDTH-1:0] exp_addr;
|
||||||
|
|
||||||
|
initial begin
|
||||||
|
rst = 1; cs_n = 1; sclk = 0; mosi = 0;
|
||||||
|
repeat (10) @(posedge clk);
|
||||||
|
rst = 0;
|
||||||
|
repeat (5) @(posedge clk);
|
||||||
|
|
||||||
|
// ================= Test A: WRITE_JOB, delayed reg_ready =====
|
||||||
|
reg_ready_model = 0;
|
||||||
|
cs_n = 0; #20;
|
||||||
|
spi_byte(8'h10, rxb); // opcode WRITE_JOB
|
||||||
|
spi_byte(8'h05, rxb); // node_id=5
|
||||||
|
spi_byte(8'h02, rxb); // required=2
|
||||||
|
spi_byte(8'hAB, rxb); // producer_ids[15:8]
|
||||||
|
spi_byte(8'hCD, rxb); // producer_ids[7:0]
|
||||||
|
spi_byte(8'h00, rxb); // x_base[22:16]
|
||||||
|
spi_byte(8'h10, rxb); // x_base[15:8]
|
||||||
|
spi_byte(8'h00, rxb); // x_base[7:0] -> x_base=0x001000
|
||||||
|
spi_byte(8'h00, rxb); // w_base[22:16]
|
||||||
|
spi_byte(8'h20, rxb); // w_base[15:8]
|
||||||
|
spi_byte(8'h00, rxb); // w_base[7:0] -> w_base=0x002000
|
||||||
|
spi_byte(8'h00, rxb); // n_tiles[15:8]
|
||||||
|
spi_byte(8'h04, rxb); // n_tiles[7:0] -> n_tiles=4
|
||||||
|
spi_byte(8'h00, rxb); // result_addr[22:16]
|
||||||
|
spi_byte(8'h30, rxb); // result_addr[15:8]
|
||||||
|
spi_byte(8'h00, rxb); // result_addr[7:0] -> result_addr=0x003000
|
||||||
|
|
||||||
|
// reg_valid must now be held (reg_ready still 0). Allow for the
|
||||||
|
// CDC synchronizer latency on the LAST bit before sampling.
|
||||||
|
repeat (8) @(posedge clk);
|
||||||
|
check(reg_valid == 1'b1, "A: reg_valid asserted after 15th payload byte");
|
||||||
|
check(reg_node_id == 5, "A: reg_node_id");
|
||||||
|
check(reg_required == 2, "A: reg_required");
|
||||||
|
check(reg_producer_ids == 16'hABCD, "A: reg_producer_ids");
|
||||||
|
check(reg_x_base == 23'h001000, "A: reg_x_base");
|
||||||
|
check(reg_w_base == 23'h002000, "A: reg_w_base");
|
||||||
|
check(reg_n_tiles == 16'h0004, "A: reg_n_tiles");
|
||||||
|
check(reg_result_addr == 23'h003000, "A: reg_result_addr");
|
||||||
|
|
||||||
|
repeat (3) begin
|
||||||
|
@(posedge clk);
|
||||||
|
check(reg_valid == 1'b1, "A: reg_valid still held while reg_ready=0");
|
||||||
|
end
|
||||||
|
reg_ready_model = 1;
|
||||||
|
@(posedge clk);
|
||||||
|
#1;
|
||||||
|
check(reg_valid == 1'b0, "A: reg_valid drops the cycle after reg_ready seen");
|
||||||
|
reg_ready_model = 0;
|
||||||
|
cs_n = 1; #40;
|
||||||
|
|
||||||
|
// ================= Test B: STATUS after accepted job ========
|
||||||
|
cs_n = 0; #20;
|
||||||
|
spi_byte(8'h20, rxb); // opcode STATUS
|
||||||
|
spi_byte(8'h00, rxb); // clocks out status byte
|
||||||
|
$monitoroff;
|
||||||
|
check(rxb[2] == 1'b1, "B: STATUS last_job_accepted=1");
|
||||||
|
check(rxb[0] == 1'b0, "B: STATUS job_busy=0 (already accepted)");
|
||||||
|
cs_n = 1; #40;
|
||||||
|
|
||||||
|
// ================= Test C: WRITE_MEM, single word ===========
|
||||||
|
cs_n = 0; #20;
|
||||||
|
spi_byte(8'h01, rxb); // opcode WRITE_MEM
|
||||||
|
spi_byte(8'h00, rxb); spi_byte(8'h00, rxb); spi_byte(8'h55, rxb); // addr=0x000055
|
||||||
|
spi_byte(8'h00, rxb); spi_byte(8'h01, rxb); // len_words=1
|
||||||
|
spi_byte(8'h12, rxb); spi_byte(8'h34, rxb); // data=0x1234
|
||||||
|
// hold CS low, idle SCLK, while the memory model latency elapses
|
||||||
|
#200;
|
||||||
|
cs_n = 1; #40;
|
||||||
|
check(mem_model[16'h0055] == 16'h1234, "C: WRITE_MEM wrote 0x1234 @ 0x000055");
|
||||||
|
|
||||||
|
// ================= Test D: READ_MEM, single word =============
|
||||||
|
cs_n = 0; #20;
|
||||||
|
spi_byte(8'h02, rxb); // opcode READ_MEM
|
||||||
|
spi_byte(8'h00, rxb); spi_byte(8'h00, rxb); spi_byte(8'h55, rxb); // addr=0x000055
|
||||||
|
spi_byte(8'h00, rxb); spi_byte(8'h01, rxb); // len_words=1
|
||||||
|
#200; // idle SCLK while the read latency elapses
|
||||||
|
spi_byte(8'h00, rxb); exp_addr = rxb; // MSB
|
||||||
|
check(rxb == 8'h12, "D: READ_MEM MSB byte == 0x12");
|
||||||
|
spi_byte(8'h00, rxb);
|
||||||
|
check(rxb == 8'h34, "D: READ_MEM LSB byte == 0x34");
|
||||||
|
cs_n = 1; #40;
|
||||||
|
|
||||||
|
// ================= Test E: RESET opcode ======================
|
||||||
|
cs_n = 0; #20;
|
||||||
|
spi_byte(8'h0F, rxb); // opcode RESET
|
||||||
|
cs_n = 1;
|
||||||
|
begin : wait_soft_rst
|
||||||
|
integer wi; reg seen;
|
||||||
|
seen = 1'b0;
|
||||||
|
for (wi = 0; wi < 10; wi = wi + 1) begin
|
||||||
|
@(posedge clk);
|
||||||
|
if (soft_rst_pulse) seen = 1'b1;
|
||||||
|
end
|
||||||
|
check(seen, "E: soft_rst_pulse asserted after CS rises (within CDC latency)");
|
||||||
|
end
|
||||||
|
|
||||||
|
$display("=== tb_spi_host_bridge: %0d/%0d PASS ===", tests-errors, tests);
|
||||||
|
if (errors != 0) $display("*** %0d FAILURES ***", errors);
|
||||||
|
$finish;
|
||||||
|
end
|
||||||
|
|
||||||
|
endmodule
|
||||||
Reference in New Issue
Block a user