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,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 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 [15:0] n_tiles_reg;
|
||||
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)
|
||||
|
||||
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 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
|
||||
// in this variant (no mux needed -- weight fetch lives on the
|
||||
// separate wide port 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;
|
||||
// separate wide port above). Declarations moved up (see above).
|
||||
|
||||
assign mem_req = wr_mem_req;
|
||||
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;
|
||||
integer total_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;
|
||||
|
||||
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)
|
||||
// and the FSM is genuinely stalled on it (not mid-read-pipeline, not
|
||||
// already holding a valid operand).
|
||||
wire [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_could_present_act;
|
||||
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 [31:0] prev_tile_idx [0:N_SLOTS_CFG-1];
|
||||
integer weight_stall_cycles [0:N_SLOTS_CFG-1];
|
||||
@@ -419,7 +424,6 @@ module tb #(
|
||||
// Director/dependency bookkeeping
|
||||
integer jobs_allocated, jobs_completed, wakeups;
|
||||
integer waiting_sum, ready_sum, dispatched_sum, sample_count;
|
||||
integer ni;
|
||||
|
||||
// Occupancy sampling is EXPENSIVE (a full N_NODES=512 scan) and is
|
||||
// only needed for the small/structural workloads (A/B/E/F), not
|
||||
|
||||
Reference in New Issue
Block a user