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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MUG92aM9m68TRc4rG55BcC
This commit is contained in:
@@ -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]
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user