Files
FPGA-Neural/hardware/v3/rtl/n2_system_ddr3_top.v
T
micheleandClaude Sonnet 5 bdc821222f 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
2026-09-20 16:33:46 +02:00

347 lines
17 KiB
Verilog

`timescale 1ns/1ps
// ============================================================
// V3 -- REAL synthesis/P&R top for the physically-interfaced N=2
// system: real DDR3 (via the public mig_7series_0 wrapper, NOT the
// sim-only _mig inner module used by this project's testbenches --
// the public wrapper always runs real calibration, SIM_BYPASS_INIT_
// CAL is not exposed/forced here, matching real board behavior) +
// spi_host_bridge_v3.v (EXP-0072) as the physical host interface +
// host_mem_bridge.v (EXP-0071) as a 3rd arbiter requester, giving the
// host a real raw-DDR3-access path alongside the 2 compute slots.
//
// Everything downstream of the MIG (adapter, arbiter, Director, both
// packed_slot instances, and the SPI bridge itself) runs in the
// ui_clk domain, per this project's own standing convention
// (mig_native_adapter.v's header) -- ui_clk is generated BY the MIG
// from sys_clk_p/n, so this module only takes sys_clk_p/n/clk_ref_p/n/
// sys_rst as clock/reset inputs, not a separate system clock.
//
// EXP-0084: sys_clk and clk_ref are now real DIFFERENTIAL pairs (the
// user's own wizard choice, confirmed against the real regenerated
// public mig_7series_0.v wrapper, which expects sys_clk_p/n and
// clk_ref_p/n directly as raw top-level differential pins -- not a
// single-ended sys_clk_i/clk_ref_i pair any more). Real board
// implication: a differential oscillator, not a single-ended one.
//
// This is the first REAL (in-context, not out-of-context) P&R target
// for V3: previous P&R runs (EXP-0059/63/67) were all out-of-context
// synthesis of a sub-block, without the real MIG-generated pin/timing
// XDC constraints -- this module plus mig_7series_0.xdc together are
// meant to be built with the genuine `vivado -mode batch` synth+impl
// flow for a real, board-accurate Fmax signoff (the user's own
// explicit request: "un timing reale... un confronto affidabile e
// veritiero").
//
// Activation stand-in ports (see packed_slot.v's own header) remain a
// disclosed, separate gap -- no real activation-fetch engine exists
// yet, so both slots' act_tile_* ports are still exposed at the top
// level rather than connected to anything internal.
// ============================================================
module n2_system_ddr3_top #(
parameter DATA_WIDTH = 8,
parameter P_IN = 8,
parameter ACC_WIDTH = 32,
parameter BURST_LEN = 8,
parameter JOB_ADDR_WIDTH = 26, // Director/packed_slot byte-base-address convention
parameter MEM_ADDR_WIDTH = 25, // arbiter/adapter word/burst-address convention
parameter LAYER_BYTES = 128,
parameter N_SLOTS = 2,
parameter QUEUE_DEPTH = 8
)(
// ---- MIG clock/reset (EXP-0084: real differential pairs, both
// the user's own wizard choice -- confirmed against the real
// regenerated public mig_7series_0.v wrapper, which now expects
// sys_clk_p/n and clk_ref_p/n directly as raw top-level
// differential pins, not a single-ended sys_clk_i/clk_ref_i pair
// any more) ----
input wire sys_clk_p,
input wire sys_clk_n,
input wire sys_rst,
input wire clk_ref_p,
input wire clk_ref_n,
// ---- real DDR3 pins (matches mig_7series_0.xdc's own port names;
// dq/dqs/dm widths doubled since EXP-0084's real 32-bit widening --
// two MT41J128M16 chips ganged in parallel, confirmed against the
// real regenerated mig_7series_0.v wrapper, not assumed) ----
inout wire [31:0] ddr3_dq,
inout wire [3:0] ddr3_dqs_n,
inout wire [3:0] ddr3_dqs_p,
output wire [13:0] ddr3_addr,
output wire [2:0] ddr3_ba,
output wire ddr3_ras_n,
output wire ddr3_cas_n,
output wire ddr3_we_n,
output wire ddr3_reset_n,
output wire [0:0] ddr3_ck_p,
output wire [0:0] ddr3_ck_n,
output wire [0:0] ddr3_cke,
output wire [0:0] ddr3_cs_n,
output wire [3:0] ddr3_dm,
output wire [0:0] ddr3_odt,
// ---- physical SPI host interface (-> spi_host_bridge_v3.v) ----
input wire sclk,
input wire mosi,
output wire miso,
input wire cs_n,
// ---- config-flash passthrough physical pins (this project's own
// board pins D00_MOSI=K17/D01_DIN=K18/FCS_B=L13, reclaimed as
// ordinary fabric I/O post-configuration -- see flash_spi_master.v's
// own header for the real Xilinx PERSIST/STARTUPE2 requirements
// this depends on). CCLK is NOT a port here -- flash_spi_master.v
// drives it internally via STARTUPE2, a dedicated pin that can
// never be an ordinary top-level port. ----
output wire flash_cs_n,
output wire flash_mosi,
input wire flash_miso,
// ---- results (small enough to keep as real top-level pins for
// observation; NOT part of the activation-interface pin-count
// problem described below) ----
output wire signed [DATA_WIDTH-1:0] s0_result_data_a,
output wire signed [DATA_WIDTH-1:0] s0_result_data_b,
output wire signed [DATA_WIDTH-1:0] s1_result_data_a,
output wire signed [DATA_WIDTH-1:0] s1_result_data_b,
// ---- status ----
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,
// ---- 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;
wire app_en, app_rdy;
wire [127:0] app_wdf_data;
wire app_wdf_end;
wire [15:0] app_wdf_mask;
wire app_wdf_wren, app_wdf_rdy;
wire [127:0] app_rd_data;
wire app_rd_data_end, app_rd_data_valid;
wire ui_clk, ui_clk_sync_rst;
assign ui_clk_o = ui_clk;
// real DDR3 memory controller -- public wrapper (always runs real
// calibration; the SIM_BYPASS_INIT_CAL override this project's
// testbenches use is only exposed on the inner _mig module, never
// instantiated here).
mig_7series_0 u_mig (
.ddr3_dq(ddr3_dq), .ddr3_dqs_n(ddr3_dqs_n), .ddr3_dqs_p(ddr3_dqs_p),
.ddr3_addr(ddr3_addr), .ddr3_ba(ddr3_ba),
.ddr3_ras_n(ddr3_ras_n), .ddr3_cas_n(ddr3_cas_n), .ddr3_we_n(ddr3_we_n),
.ddr3_reset_n(ddr3_reset_n),
.ddr3_ck_p(ddr3_ck_p), .ddr3_ck_n(ddr3_ck_n),
.ddr3_cke(ddr3_cke), .ddr3_cs_n(ddr3_cs_n),
.ddr3_dm(ddr3_dm), .ddr3_odt(ddr3_odt),
.sys_clk_p(sys_clk_p), .sys_clk_n(sys_clk_n), .clk_ref_p(clk_ref_p), .clk_ref_n(clk_ref_n),
.app_addr(app_addr), .app_cmd(app_cmd), .app_en(app_en),
.app_wdf_data(app_wdf_data), .app_wdf_end(app_wdf_end),
.app_wdf_mask(app_wdf_mask), .app_wdf_wren(app_wdf_wren),
.app_rd_data(app_rd_data), .app_rd_data_end(app_rd_data_end),
.app_rd_data_valid(app_rd_data_valid), .app_rdy(app_rdy), .app_wdf_rdy(app_wdf_rdy),
.app_sr_req(1'b0), .app_ref_req(1'b0), .app_zq_req(1'b0),
.app_sr_active(), .app_ref_ack(), .app_zq_ack(),
.ui_clk(ui_clk), .ui_clk_sync_rst(ui_clk_sync_rst),
.init_calib_complete(init_calib_complete),
.device_temp(),
.sys_rst(sys_rst)
);
wire adp_req, adp_wr;
wire [MEM_ADDR_WIDTH-1:0] adp_addr;
wire [32*BURST_LEN-1:0] adp_wdata;
wire [4*BURST_LEN-1:0] adp_wmask;
wire [32*BURST_LEN-1:0] adp_rdata;
wire adp_ready, adp_busy;
mig_native_adapter #(.BURST_LEN(BURST_LEN), .ADDR_WIDTH(MEM_ADDR_WIDTH)) u_adapter (
.clk(ui_clk), .rst(ui_clk_sync_rst),
.req(adp_req), .wr(adp_wr), .addr(adp_addr), .wdata(adp_wdata), .wmask(adp_wmask),
.rdata(adp_rdata), .ready(adp_ready), .busy(adp_busy),
.app_addr(app_addr), .app_cmd(app_cmd), .app_en(app_en), .app_rdy(app_rdy),
.app_wdf_data(app_wdf_data), .app_wdf_end(app_wdf_end), .app_wdf_mask(app_wdf_mask),
.app_wdf_wren(app_wdf_wren), .app_wdf_rdy(app_wdf_rdy),
.app_rd_data(app_rd_data), .app_rd_data_end(app_rd_data_end), .app_rd_data_valid(app_rd_data_valid)
);
// ---- 3-way arbiter: slot0, slot1, host_mem_bridge (SPI raw access) ----
localparam NUM_REQ = 3;
wire [NUM_REQ-1:0] req_active, req_grant, req_req, req_wr;
wire [NUM_REQ-1:0] req_ready, req_busy;
wire [NUM_REQ*MEM_ADDR_WIDTH-1:0] req_addr;
wire [NUM_REQ*32*BURST_LEN-1:0] req_wdata;
wire [NUM_REQ*4*BURST_LEN-1:0] req_wmask;
wire [NUM_REQ*32*BURST_LEN-1:0] req_rdata;
sdram_arbiter_n #(
.NUM_REQ(NUM_REQ), .ADDR_WIDTH(MEM_ADDR_WIDTH), .BURST_LEN(BURST_LEN)
) u_arb (
.clk(ui_clk), .rst(ui_clk_sync_rst),
.req_active(req_active), .req_grant(req_grant),
.req_req(req_req), .req_wr(req_wr), .req_addr(req_addr),
.req_wdata(req_wdata), .req_wmask(req_wmask),
.req_rdata(req_rdata), .req_ready(req_ready), .req_busy(req_busy),
.ctrl_req(adp_req), .ctrl_wr(adp_wr), .ctrl_addr(adp_addr),
.ctrl_wdata(adp_wdata), .ctrl_wmask(adp_wmask),
.ctrl_rdata(adp_rdata), .ctrl_ready(adp_ready), .ctrl_busy(adp_busy)
);
// ---- Director + job submission (fed directly by the SPI bridge, same clock domain) ----
wire job_in_valid, job_in_ready;
wire [JOB_ADDR_WIDTH-1:0] job_in_x_base, job_in_w_base, job_in_result_addr;
wire [15:0] job_in_n_tiles, job_in_node_id;
wire [N_SLOTS-1:0] slot_job_start;
wire [JOB_ADDR_WIDTH*N_SLOTS-1:0] slot_x_base_a, slot_x_base_b, slot_w_base;
wire [JOB_ADDR_WIDTH*N_SLOTS-1:0] slot_result_addr_a, slot_result_addr_b;
wire [16*N_SLOTS-1:0] slot_n_tiles, slot_node_id_a, slot_node_id_b;
wire [N_SLOTS-1:0] slot_job_done;
wire [3:0] dir_state;
wire dir_error;
wire queue_empty;
neural_director_packed #(
.ADDR_WIDTH(JOB_ADDR_WIDTH), .N_SLOTS(N_SLOTS), .QUEUE_DEPTH(QUEUE_DEPTH)
) u_dir (
.clk(ui_clk), .rst(ui_clk_sync_rst),
.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),
.job_in_node_id(job_in_node_id),
.slot_job_start(slot_job_start),
.slot_x_base_a(slot_x_base_a), .slot_x_base_b(slot_x_base_b),
.slot_w_base(slot_w_base), .slot_n_tiles(slot_n_tiles),
.slot_result_addr_a(slot_result_addr_a), .slot_result_addr_b(slot_result_addr_b),
.slot_node_id_a(slot_node_id_a), .slot_node_id_b(slot_node_id_b),
.slot_job_done(slot_job_done),
.job_out_done(job_out_done), .job_out_slot(job_out_slot),
.dir_state(dir_state), .dir_error(dir_error), .queue_empty(queue_empty)
);
// ---- physical SPI host interface: submits jobs to the Director,
// and drives req[2] (host_mem_bridge.v) for raw DDR3 access ----
wire mem_req, mem_wr, mem_lb_n, mem_ub_n, mem_ready;
wire [MEM_ADDR_WIDTH-1:0] mem_addr;
wire [15:0] mem_wdata, mem_rdata;
wire soft_rst_pulse;
wire flash_xfer_active, flash_byte_req, flash_byte_done;
wire [7:0] flash_byte_wdata, flash_byte_rdata;
spi_host_bridge_v3 #(
.JOB_ADDR_WIDTH(JOB_ADDR_WIDTH), .MEM_ADDR_WIDTH(MEM_ADDR_WIDTH), .N_SLOTS(N_SLOTS)
) u_spi (
.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),
.job_in_node_id(job_in_node_id),
.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), .mem_ready(mem_ready),
.flash_xfer_active(flash_xfer_active), .flash_byte_req(flash_byte_req),
.flash_byte_wdata(flash_byte_wdata), .flash_byte_rdata(flash_byte_rdata),
.flash_byte_done(flash_byte_done),
.soft_rst_pulse(soft_rst_pulse)
);
flash_spi_master u_flash (
.clk(ui_clk), .rst(ui_clk_sync_rst),
.xfer_active(flash_xfer_active), .byte_req(flash_byte_req),
.byte_wdata(flash_byte_wdata), .byte_rdata(flash_byte_rdata), .byte_done(flash_byte_done), .busy(),
.flash_cs_n(flash_cs_n), .flash_mosi(flash_mosi), .flash_miso(flash_miso)
);
host_mem_bridge #(
.BURST_LEN(BURST_LEN), .ADDR_WIDTH(MEM_ADDR_WIDTH)
) u_host_bridge (
.clk(ui_clk), .rst(ui_clk_sync_rst),
.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), .mem_ready(mem_ready),
.req_active(req_active[2]), .req_grant(req_grant[2]),
.req_req(req_req[2]), .req_wr(req_wr[2]),
.req_addr(req_addr[2*MEM_ADDR_WIDTH +: MEM_ADDR_WIDTH]),
.req_wdata(req_wdata[2*32*BURST_LEN +: 32*BURST_LEN]),
.req_wmask(req_wmask[2*4*BURST_LEN +: 4*BURST_LEN]),
.req_rdata(req_rdata[2*32*BURST_LEN +: 32*BURST_LEN]),
.req_ready(req_ready[2]), .req_busy(req_busy[2])
);
wire [15:0] s0_nid_a, s0_nid_b, s1_nid_a, s1_nid_b;
wire [JOB_ADDR_WIDTH-1:0] s0_raddr_a, s0_raddr_b, s1_raddr_a, s1_raddr_b;
// ---- activation fetch: REAL now (EXP-0079) -- each packed_slot
// instance owns its own act_tile_fetch.v internally, sharing that
// SAME slot's existing ctrl_req/addr/etc port (already wired to
// the arbiter below) with its own weight-prefetch engine. No
// top-level activation ports exist any more -- the old stand-in
// (act_tile_addr_a/b -> act_tile_data_a/b, and before that, a
// free-running counter stub that nearly blew the package's whole
// I/O budget, see git history) is gone; this is fully internal.
packed_slot #(
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .ACC_WIDTH(ACC_WIDTH),
.BURST_LEN(BURST_LEN), .ADDR_WIDTH(JOB_ADDR_WIDTH), .LAYER_BYTES(LAYER_BYTES)
) u_slot0 (
.clk(ui_clk), .rst(ui_clk_sync_rst),
.job_start(slot_job_start[0]),
.x_base_a(slot_x_base_a[0*JOB_ADDR_WIDTH +: JOB_ADDR_WIDTH]),
.x_base_b(slot_x_base_b[0*JOB_ADDR_WIDTH +: JOB_ADDR_WIDTH]),
.w_base(slot_w_base[0*JOB_ADDR_WIDTH +: JOB_ADDR_WIDTH]),
.n_tiles(slot_n_tiles[0*16 +: 16]),
.result_addr_a(slot_result_addr_a[0*JOB_ADDR_WIDTH +: JOB_ADDR_WIDTH]),
.result_addr_b(slot_result_addr_b[0*JOB_ADDR_WIDTH +: JOB_ADDR_WIDTH]),
.node_id_a(slot_node_id_a[0*16 +: 16]), .node_id_b(slot_node_id_b[0*16 +: 16]),
.job_done(slot_job_done[0]),
.result_data_a(s0_result_data_a), .result_data_b(s0_result_data_b),
.result_node_id_a(s0_nid_a), .result_node_id_b(s0_nid_b),
.result_addr_a_out(s0_raddr_a), .result_addr_b_out(s0_raddr_b),
.mem_active(req_active[0]), .mem_grant(req_grant[0]),
.ctrl_req(req_req[0]), .ctrl_wr(req_wr[0]),
.ctrl_addr(req_addr[0*MEM_ADDR_WIDTH +: MEM_ADDR_WIDTH]),
.ctrl_wdata(req_wdata[0*32*BURST_LEN +: 32*BURST_LEN]),
.ctrl_wmask(req_wmask[0*4*BURST_LEN +: 4*BURST_LEN]),
.ctrl_rdata(req_rdata[0*32*BURST_LEN +: 32*BURST_LEN]),
.ctrl_ready(req_ready[0]), .ctrl_busy(req_busy[0])
);
packed_slot #(
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .ACC_WIDTH(ACC_WIDTH),
.BURST_LEN(BURST_LEN), .ADDR_WIDTH(JOB_ADDR_WIDTH), .LAYER_BYTES(LAYER_BYTES)
) u_slot1 (
.clk(ui_clk), .rst(ui_clk_sync_rst),
.job_start(slot_job_start[1]),
.x_base_a(slot_x_base_a[1*JOB_ADDR_WIDTH +: JOB_ADDR_WIDTH]),
.x_base_b(slot_x_base_b[1*JOB_ADDR_WIDTH +: JOB_ADDR_WIDTH]),
.w_base(slot_w_base[1*JOB_ADDR_WIDTH +: JOB_ADDR_WIDTH]),
.n_tiles(slot_n_tiles[1*16 +: 16]),
.result_addr_a(slot_result_addr_a[1*JOB_ADDR_WIDTH +: JOB_ADDR_WIDTH]),
.result_addr_b(slot_result_addr_b[1*JOB_ADDR_WIDTH +: JOB_ADDR_WIDTH]),
.node_id_a(slot_node_id_a[1*16 +: 16]), .node_id_b(slot_node_id_b[1*16 +: 16]),
.job_done(slot_job_done[1]),
.result_data_a(s1_result_data_a), .result_data_b(s1_result_data_b),
.result_node_id_a(s1_nid_a), .result_node_id_b(s1_nid_b),
.result_addr_a_out(s1_raddr_a), .result_addr_b_out(s1_raddr_b),
.mem_active(req_active[1]), .mem_grant(req_grant[1]),
.ctrl_req(req_req[1]), .ctrl_wr(req_wr[1]),
.ctrl_addr(req_addr[1*MEM_ADDR_WIDTH +: MEM_ADDR_WIDTH]),
.ctrl_wdata(req_wdata[1*32*BURST_LEN +: 32*BURST_LEN]),
.ctrl_wmask(req_wmask[1*4*BURST_LEN +: 4*BURST_LEN]),
.ctrl_rdata(req_rdata[1*32*BURST_LEN +: 32*BURST_LEN]),
.ctrl_ready(req_ready[1]), .ctrl_busy(req_busy[1])
);
endmodule