From bdc821222f4f2136df0596d7d83f30e12b8a4163 Mon Sep 17 00:00:00 2001 From: manvalan Date: Sun, 20 Sep 2026 16:33:46 +0200 Subject: [PATCH] feat: real active-low data_ready_n sticky IRQ pin (EXP-0085) User-requested hardware notification so the ESP32 can be interrupt-driven instead of polling STATUS in a loop. spi_host_bridge_v3.v: new job_out_done input (wired from neural_director_packed.v, already available at the top level) and new data_ready_n output. A sticky irq_pending register sets on job_out_done (latched, survives the pulse itself deasserting) and clears when the host completes a real STATUS (0x20) or REG_READ(0x02) transaction - reusing cs_rose, the same real transaction-complete event the module already relies on elsewhere, not a new mechanism. dir_error is ORed in live/combinational, not latched. SET has priority over CLEAR on the rare cycle both coincide. Real pin: D14, bank 15 (already 3.3V, alongside the SPI bus and sys_rst) - tentative, not yet a final board decision. Deliberately added after EXP-0084's own P&R iterations settled, so it didn't complicate that already-tight I/O/VCCO budget mid-fix. Its own real P&R verification is deferred to the next real P&R run (already needed to close EXP-0084's clock-period timing gap), not run separately against a config already known to fail timing for unrelated reasons. Real verification: tb_spi_host_bridge_v3.v extended with 10 new checks (idle state, sticky set, mid-transaction hold, real-acknowledge clear, unrelated-register non-acknowledge, dir_error live assert/clear). 49/49 PASS. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01MUG92aM9m68TRc4rG55BcC --- docs/PHYSICAL_REALIZATION.md | 27 +++++++-- hardware/v2/logs/experiments.log | 58 ++++++++++++++++++ .../v3/constraints/n2_system_ddr3_top.xdc | 9 +++ hardware/v3/rtl/n2_system_ddr3_top.v | 9 ++- hardware/v3/rtl/spi_host_bridge_v3.v | 40 +++++++++++++ hardware/v3/sim/tb_spi_host_bridge_v3.v | 60 +++++++++++++++++++ 6 files changed, 196 insertions(+), 7 deletions(-) diff --git a/docs/PHYSICAL_REALIZATION.md b/docs/PHYSICAL_REALIZATION.md index f32ae51..052cb4d 100644 --- a/docs/PHYSICAL_REALIZATION.md +++ b/docs/PHYSICAL_REALIZATION.md @@ -87,6 +87,8 @@ Bank 15, package edge column A/B, physically adjacent (short traces), `LVCMOS33` | mosi | B16 | input | | miso | B17 | output | | cs_n | A16 | input | +| sys_rst | G13 | input (EXP-0084, tentative — see §7) | +| data_ready_n | D14 | output (EXP-0085, active-low sticky IRQ — see §6, tentative — see §7) | **Bank 15 VCCO**: assumed **3.3V** — change the XDC's IOSTANDARD if your board power plan uses a different rail for this bank. @@ -334,6 +336,19 @@ Full byte-level field layouts are documented in `spi_host_bridge_v3.v`'s own header comment — treat that file as the authoritative protocol spec, this table is a summary/index. +**Real out-of-band notification (EXP-0085)**: `data_ready_n`, a real, +**active-low, sticky** top-level pin (D14, bank 15, 3.3V — see §2.2) — +lets the ESP32 be interrupt-driven instead of polling STATUS in a loop. +Asserted (driven low) when a job/pair completes (`job_out_done`) or while +a real Director error is active (`dir_error`); the job-completion latch +is **sticky** (stays low even after the underlying pulse ends) until the +host acknowledges by completing a real STATUS (0x20) or REG_READ(0x02) +transaction — a REG_READ of any *other* register does not acknowledge it. +The `dir_error` contribution is live/combinational, not latched — it +clears the moment `dir_error` itself does. Real firmware implication: the +ESP32 can wire this to a GPIO interrupt and only bother reading STATUS +when it actually fires, instead of polling every loop iteration. + ## 7. Known-open items (honestly disclosed, not hidden) - Scaling past N=2 compute cores (silicon budget allows up to ~30 per the @@ -359,9 +374,9 @@ table is a summary/index. `docs/ARCHITECTURE_ANALYSIS.md` §5.2 for the honest number and why. The larger multi-slot DDRManager is not built. Its own real benefit should be re-measured once the 32-bit channel's timing actually closes. -- User-requested, not yet built: an active-low `data_ready_n`/IRQ pin so - the ESP32 can be interrupt-driven (job completion or error pending) - instead of polling the STATUS register — planned as a sticky bit (set on - `job_out_done`/`dir_error`, cleared when the ESP32 reads STATUS), - deliberately deferred until the §3.1 timing-closure work lands so it - doesn't further complicate the currently-tight I/O/VCCO budget mid-fix. +- `data_ready_n` (EXP-0085, user-requested active-low sticky IRQ) is real, + built, and functionally verified (49/49 including 10 new checks, + `tb_spi_host_bridge_v3.v`) — see §6. Its own real P&R verification is + deferred to the same next real P&R run already needed to close §3.1's + timing gap, not run separately against a config already known to fail + timing for unrelated reasons. diff --git a/hardware/v2/logs/experiments.log b/hardware/v2/logs/experiments.log index 7366e9c..5c68d82 100644 --- a/hardware/v2/logs/experiments.log +++ b/hardware/v2/logs/experiments.log @@ -5559,3 +5559,61 @@ neural_processor_packed.v's own packed-MAC accumulation tree (the real bottleneck identified above, unchanged since EXP-0059) would need real re-pipelining -- a genuinely separate, disclosed, not-yet- attempted optimization. + +EXP-0085 -- real active-low data_ready_n IRQ pin, user-requested +(2026-09-20, same autonomous continuation: "magari attivo basso" after +"mi aggiungi anche un pin data_ready quando ha finito la elaborazione +o semplicemente FPGA ha qualcosa da dire a processore centrale") + +CONTEXT: the ESP32 host currently has no way to know a job (or job +pair) completed, or that a real director error occurred, without +polling the STATUS register (REG_READ 0x02) in a loop. User asked for +a real hardware notification pin instead, explicitly active-low. + +DESIGN: spi_host_bridge_v3.v gained a new input (job_out_done, wired +from neural_director_packed.v's own existing output, already available +at n2_system_ddr3_top.v) and a new output (data_ready_n). A single +`irq_pending` register is SET on job_out_done (a real job/pair +completion, latched -- stays set even after job_out_done itself drops +back to 0 the next cycle) and CLEARED when the host actually completes +a STATUS-carrying transaction (STATUS opcode 0x20, or REG_READ of +register 0x02) -- reusing `cs_rose`, the SAME real "response actually +delivered" event this module's own FSM already relies on elsewhere, not +a new mechanism. SET has priority over CLEAR on the rare cycle both +coincide, so a real completion is never silently dropped by a +coincidental acknowledge. dir_error is ORed in combinationally (not +latched -- neural_director_packed.v already owns that error state's own +lifetime), so data_ready_n also tracks it directly, live. + +REAL, DELIBERATE SEQUENCING: added AFTER EXP-0084's real P&R attempts +finished (even though that P&R's own timing doesn't close yet, for +reasons unrelated to this pin) rather than interleaved with them -- +EXP-0084 spent several real iterations fighting a very tight I/O/VCCO +budget on this package; adding another top-level port mid-fight would +have made root-causing harder. Real pin assigned now: D14 (bank 15, +already-committed 3.3V, alongside the management SPI bus and sys_rst) +-- a real, verified-free pin, not yet a final board decision. + +VERIFICATION: tb_spi_host_bridge_v3.v extended with a new Test O (10 +real checks): idle-high with no job/error; job_out_done sets it low and +it's sticky (survives job_out_done itself deasserting); still low +mid-transaction, only clears when CS actually rises on a real STATUS +or REG_READ(0x02) transaction; a REG_READ of an UNRELATED register does +NOT acknowledge it; dir_error alone (no job_out_done) also asserts it, +combinationally, live, clearing the moment dir_error itself clears. +**49/49 PASS** (39 pre-existing + 10 new), real iverilog run. + +DECISION: keep. Real, verified, low-risk (one new register, one new +top-level port, no change to any existing timing-critical path). Real +P&R verification for this specific addition is deferred to the SAME +next real P&R run already needed to close EXP-0084's own real timing +gap (reverting Input Clock Period) -- no point spending a separate real +P&R cycle on an unrelated port addition against a config already known +to fail timing for other reasons. + +next_action: include in the next real P&R run (after the user's own +clock-period-revert MIG wizard session) and confirm it doesn't disturb +the tight I/O/VCCO budget further. Document the real ESP32-side +GPIO/interrupt wiring implication once the board's own reset-circuit +pin planning (S7, still open) is decided, since data_ready_n and +sys_rst now share bank 15's own real, tentative pin choices. diff --git a/hardware/v3/constraints/n2_system_ddr3_top.xdc b/hardware/v3/constraints/n2_system_ddr3_top.xdc index 2849fa7..3fd13c8 100644 --- a/hardware/v3/constraints/n2_system_ddr3_top.xdc +++ b/hardware/v3/constraints/n2_system_ddr3_top.xdc @@ -95,3 +95,12 @@ set_property IOSTANDARD LVCMOS33 [get_ports cs_n] # (queried from the actual part database). set_property PACKAGE_PIN G13 [get_ports sys_rst] set_property IOSTANDARD LVCMOS33 [get_ports sys_rst] + +# ---- data_ready_n: real, user-requested active-low sticky IRQ so the +# ESP32 can be interrupt-driven instead of polling STATUS (see +# spi_host_bridge_v3.v's own header for the real set/clear semantics). +# Placed in bank 15 alongside the management SPI bus (same real, +# already-committed 3.3V) -- real, verified-free pin, not yet a final +# board decision (same caveat as sys_rst above). +set_property PACKAGE_PIN D14 [get_ports data_ready_n] +set_property IOSTANDARD LVCMOS33 [get_ports data_ready_n] diff --git a/hardware/v3/rtl/n2_system_ddr3_top.v b/hardware/v3/rtl/n2_system_ddr3_top.v index c440a76..3b1f670 100644 --- a/hardware/v3/rtl/n2_system_ddr3_top.v +++ b/hardware/v3/rtl/n2_system_ddr3_top.v @@ -110,7 +110,13 @@ module n2_system_ddr3_top #( output wire ui_clk_o, output wire init_calib_complete, output wire job_out_done, - output wire [$clog2(N_SLOTS)-1:0] job_out_slot + output wire [$clog2(N_SLOTS)-1:0] job_out_slot, + + // ---- host notification (real feature, user-requested, EXP-0084): + // active-low, sticky IRQ so the ESP32 can be interrupt-driven + // instead of polling STATUS. See spi_host_bridge_v3.v's own header + // for the real set/clear semantics. ---- + output wire data_ready_n ); wire [27:0] app_addr; wire [2:0] app_cmd; @@ -238,6 +244,7 @@ module n2_system_ddr3_top #( .clk(ui_clk), .rst(ui_clk_sync_rst), .sclk(sclk), .mosi(mosi), .miso(miso), .cs_n(cs_n), .init_calib_complete(init_calib_complete), .dir_error(dir_error), + .job_out_done(job_out_done), .data_ready_n(data_ready_n), .job_in_valid(job_in_valid), .job_in_ready(job_in_ready), .job_in_x_base(job_in_x_base), .job_in_w_base(job_in_w_base), .job_in_n_tiles(job_in_n_tiles), .job_in_result_addr(job_in_result_addr), diff --git a/hardware/v3/rtl/spi_host_bridge_v3.v b/hardware/v3/rtl/spi_host_bridge_v3.v index 788e947..5f57cda 100644 --- a/hardware/v3/rtl/spi_host_bridge_v3.v +++ b/hardware/v3/rtl/spi_host_bridge_v3.v @@ -195,6 +195,17 @@ module spi_host_bridge_v3 #( // ---- system status, for the REG 0x02 STATUS register ---- input wire init_calib_complete, input wire dir_error, + input wire job_out_done, // one-cycle pulse (-> neural_director_packed.v), sets the sticky IRQ below + + // ---- host notification (real feature, user-requested): active-low, + // sticky IRQ so the ESP32 can be interrupt-driven instead of polling + // STATUS. Set on job_out_done (a real job/pair completed) or while + // dir_error is active; cleared when the host acknowledges by + // completing a STATUS (0x20) or REG_READ of STATUS (0x31, reg 0x02) + // transaction -- the SAME real event this module already uses + // (cs_rose) to know a response was actually delivered, not just + // requested. ---- + output wire data_ready_n, // ---- physical SPI pins ---- input wire sclk, @@ -660,4 +671,33 @@ module spi_host_bridge_v3 #( end end + // ============================================================ + // HOST NOTIFICATION (data_ready_n, real feature, user-requested): + // a sticky, active-low IRQ line so the ESP32 can be interrupt- + // driven instead of polling STATUS every loop. SET on job_out_done + // (a real job/pair completed -- job_busy/STATUS's own bit0 already + // drops the SAME cycle, but that's a level the host would have to + // catch at exactly the right moment; this latches it). Acknowledged + // (CLEARED) when the host actually receives a STATUS-carrying + // response -- reusing cs_rose, the SAME real "transaction actually + // delivered" event this module's own FSM already relies on, not a + // separate mechanism. SET has priority over CLEAR on the rare cycle + // both coincide, so a real completion is never silently dropped. + // dir_error is ORed in combinationally (a real, level-held director + // error condition) on top of the latched bit, not itself latched + // here -- neural_director_packed.v owns its own error state. + // ============================================================ + reg irq_pending; + wire ack_now = cs_rose && ((opcode == OP_STATUS) || + (opcode == OP_REG_READ && reg_addr == 8'h02)); + always @(posedge clk) begin + if (rst) begin + irq_pending <= 1'b0; + end else begin + if (job_out_done) irq_pending <= 1'b1; + else if (ack_now) irq_pending <= 1'b0; + end + end + assign data_ready_n = ~(irq_pending | dir_error); + endmodule diff --git a/hardware/v3/sim/tb_spi_host_bridge_v3.v b/hardware/v3/sim/tb_spi_host_bridge_v3.v index 24ba66c..aa23340 100644 --- a/hardware/v3/sim/tb_spi_host_bridge_v3.v +++ b/hardware/v3/sim/tb_spi_host_bridge_v3.v @@ -43,6 +43,8 @@ module tb_spi_host_bridge_v3; wire soft_rst_pulse; reg init_calib_complete_model = 0; reg dir_error_model = 0; + reg job_out_done_model = 0; + wire data_ready_n; // ---- config-flash passthrough path: real flash_spi_master.v + // the same behavioral W25Q32JV-like model used standalone in @@ -71,6 +73,7 @@ module tb_spi_host_bridge_v3; .flash_byte_wdata(flash_byte_wdata), .flash_byte_rdata(flash_byte_rdata), .flash_byte_done(flash_byte_done), .sclk(sclk), .mosi(mosi), .miso(miso), .cs_n(cs_n), .init_calib_complete(init_calib_complete_model), .dir_error(dir_error_model), + .job_out_done(job_out_done_model), .data_ready_n(data_ready_n), .job_in_valid(job_in_valid), .job_in_ready(job_in_ready_model), .job_in_x_base(job_in_x_base), .job_in_w_base(job_in_w_base), .job_in_n_tiles(job_in_n_tiles), .job_in_result_addr(job_in_result_addr), @@ -367,6 +370,63 @@ module tb_spi_host_bridge_v3; check(rxb == 8'h5A, "N: FLASH_XFER end-to-end round trip through the real flash model, bit-exact"); cs_n = 1; #40; + // ================= Test O: data_ready_n sticky IRQ (real + // feature, user-requested, EXP-0084) ========================== + check(data_ready_n === 1'b1, "O1: data_ready_n idle-high (no job done, no dir_error)"); + + // a real job/pair completion sets it, and it STAYS set (sticky) + // even after job_out_done itself drops back to 0. + job_out_done_model = 1'b1; + #10; + job_out_done_model = 1'b0; + #10; + check(data_ready_n === 1'b0, "O2: job_out_done sets data_ready_n low, and it's sticky (job_out_done already deasserted)"); + + // acknowledged by a real completed STATUS (0x20) transaction -- + // clears the moment CS rises on that transaction, not before. + cs_n = 0; #20; + spi_byte(8'h20, rxb); // opcode STATUS + check(data_ready_n === 1'b0, "O3: data_ready_n still low mid-transaction (not yet acknowledged)"); + cs_n = 1; #40; + check(data_ready_n === 1'b1, "O4: STATUS transaction completing (CS rise) clears data_ready_n"); + + // REG_READ of STATUS (reg 0x02) is an equally valid acknowledge. + job_out_done_model = 1'b1; + #10; + job_out_done_model = 1'b0; + #10; + check(data_ready_n === 1'b0, "O5: job_out_done sets data_ready_n low again"); + cs_n = 0; #20; + spi_byte(8'h31, rxb); // opcode REG_READ + spi_byte(8'h02, rxb); // reg_addr = 0x02 STATUS + spi_byte(8'h00, rxb); spi_byte(8'h00, rxb); spi_byte(8'h00, rxb); spi_byte(8'h00, rxb); + cs_n = 1; #40; + check(data_ready_n === 1'b1, "O6: REG_READ(STATUS) completing also clears data_ready_n"); + + // REG_READ of an UNRELATED register must NOT acknowledge it. + job_out_done_model = 1'b1; + #10; + job_out_done_model = 1'b0; + #10; + cs_n = 0; #20; + spi_byte(8'h31, rxb); + spi_byte(8'h00, rxb); // reg_addr = 0x00 DEVICE_ID, not STATUS + spi_byte(8'h00, rxb); spi_byte(8'h00, rxb); spi_byte(8'h00, rxb); spi_byte(8'h00, rxb); + cs_n = 1; #40; + check(data_ready_n === 1'b0, "O7: REG_READ of an unrelated register does NOT clear data_ready_n"); + + // acknowledge it for real now, then confirm dir_error alone + // (no job_out_done) also asserts it, combinationally, and that + // it clears again once dir_error itself deasserts. + cs_n = 0; #20; spi_byte(8'h20, rxb); cs_n = 1; #40; + check(data_ready_n === 1'b1, "O8: real acknowledge clears the pending job_out_done from O7"); + dir_error_model = 1'b1; + #10; + check(data_ready_n === 1'b0, "O9: dir_error alone (no job_out_done) also asserts data_ready_n"); + dir_error_model = 1'b0; + #10; + check(data_ready_n === 1'b1, "O10: data_ready_n clears once dir_error itself deasserts"); + $display("=== tb_spi_host_bridge_v3: %0d/%0d PASS ===", tests-errors, tests); if (errors != 0) $display("*** %0d FAILURES ***", errors); $finish;