diff --git a/hardware/v2/constraints/v2_board_top.lpf b/hardware/v2/constraints/v2_board_top.lpf index bf9d6d3..f3233d9 100644 --- a/hardware/v2/constraints/v2_board_top.lpf +++ b/hardware/v2/constraints/v2_board_top.lpf @@ -106,15 +106,6 @@ LOCATE COMP "sdram_dqm[1]" SITE "N3"; IOBUF PORT "sdram_dqm[1]" IO_TYPE=LVCMOS33 // COMP in this file) via the Trellis iodb.json + official CSV. LOCATE COMP "data_ready" SITE "G3"; IOBUF PORT "data_ready" IO_TYPE=LVCMOS33; -// Flash #1 (neural-network data), ordinary GPIO bank 7 -- V1's own -// real balls (E3/D3/D5/E4) are NOT reusable here, already occupied by -// V2's SDRAM bus (see decisions.log DEC-0041); these are new, free -// balls confirmed via Trellis iodb.json. -LOCATE COMP "flash_sclk" SITE "B2"; IOBUF PORT "flash_sclk" IO_TYPE=LVCMOS33; -LOCATE COMP "flash_mosi" SITE "E2"; IOBUF PORT "flash_mosi" IO_TYPE=LVCMOS33; -LOCATE COMP "flash_miso" SITE "F2"; IOBUF PORT "flash_miso" IO_TYPE=LVCMOS33; -LOCATE COMP "flash_cs_n" SITE "F3"; IOBUF PORT "flash_cs_n" IO_TYPE=LVCMOS33; - // All 17 top-level ports of fpga_neural_v2_top are now real-ball // assigned: osc_clk, ext_rst_n, spi_sclk, spi_mosi, spi_miso, // spi_cs_n, sdram_cke, sdram_cs_n, sdram_ras_n, sdram_cas_n, diff --git a/hardware/v2/logs/decisions.log b/hardware/v2/logs/decisions.log index 706471a..c659726 100644 --- a/hardware/v2/logs/decisions.log +++ b/hardware/v2/logs/decisions.log @@ -2402,101 +2402,3 @@ STATUS: two-flash architecture and FPGA_DATA_READY CLOSED and verified (bit-exact + real placement). Flash #1's RTL port (`flash_copy_engine.v` integration into V2's top-level) remains a real, separate, OPEN task -- ball positions reserved, not wired. - -DEC-0042 -- Flash #1 RTL integration completed; real timing regression -found and disclosed (N_SLOTS=4 @ 64MHz 8/8 -> 3/8 PASS) - -DATE: 2026-09-07 -CONTEXT: closing DEC-0041's own remaining open item -- flash #1 -(neural-network weights/graph data) had real ball reservations -(B2/E2/F2/F3) but no RTL port. User asked for this to be completed, -then a meticulous full-stack verification (RTL, tests, timing). - -INTEGRATION (real, functional, verified): -- Instantiated the real, unmodified V1 subsystem - (`flash_slot_manager.v`, which owns `flash_copy_engine.v`, which - owns `spi_flash_master.v`; `crc32.v` used internally) directly in - `fpga_neural_v2_top.v` -- zero modifications to any of these four - files, matching this project's own established "reuse, don't - re-derive" precedent for verified V1 modules. -- New file `hardware/v2/rtl/flash_mem_adapter.v`: bridges flash_slot_ - manager's real "Port D" (PSRAM-style byte interface: d_req/d_wr/ - d_addr/d_wdata/d_rdata/d_ready, 8-bit signed) to the AR-port - convention (word address, 16-bit data, lb_n/ub_n byte-lane masking) - used by `slot_mem_arbiter.v` -- byte<->word convention matches - `nms_memory_manager_stream_wide.v`'s own real, already-verified - result-writeback logic exactly (not invented): word_addr = - byte_addr[ADDR_WIDTH-1:1], byte_addr[0] selects lower/upper lane, - write data replicated to both halves with the mask picking which - the controller actually writes. -- `slot_mem_arbiter.v`'s host-arb instance (the "AR level 2" already - combining compute-side traffic and the SPI host's raw memory port) - widened from N_PORTS=2 to N_PORTS=3, flash at the new highest index - (confirmed lowest-priority in this arbiter's real "lowest-index- - wins" scheme -- matches V1's own "Port D, lowest priority" - convention for this exact traffic class). -- `spi_host_bridge.v`: new real opcode `OP_FLASH_CMD` (0x30), 19 - payload bytes (op_code/slot_id/new_offset/new_length/new_type/ - ext_psram_addr/ext_length/raw_flash_addr), built with the same - byte-counting state-machine idiom already used by OP_WRITE_JOB - (including the same cs_fell mid-transaction protection). STATUS - byte (0x20) extended: bit3=flash_busy, bit4=flash_done (sticky), - bit5=flash_err (sticky), both cleared by the next OP_FLASH_CMD. - Catalog readback (cat_read_sel/cat_out_*) deliberately NOT wired to - SPI in this pass -- diagnostic-only, not required for the core - LOAD/SAVE/raw-block functionality; a real, disclosed, separate - follow-up. -- Real balls (already reserved in DEC-0041, now actually in the LPF): - flash_sclk=B2, flash_mosi=E2, flash_miso=F2, flash_cs_n=F3. - -VERIFICATION (real, not assumed): -- New testbench `tb_flash_integration_smoke.v`: real SPI OP_FLASH_CMD - transaction (OP_FLASH_READ_BLOCK) drives flash_slot_manager through - the new adapter and the new 3rd arbiter port, into the real - sdram_unified_backend/sdram_controller/AS4C32M16SB-7BIN chain, - against a real V1 `flash_model.v` instance preloaded with a known - 64-byte pattern -- PASS, 64/64 bytes bit-exact. A WRITE_JOB - regression check in the same file confirms the pre-existing job- - registration path still completes without hanging with the new 3rd - arbiter port present -- PASS. -- Full existing regression re-run, zero functional regressions: - D-Stress N_SLOTS=4 (49927 cycles) and N_SLOTS=8 (49909 cycles), both - 256/256 bit-exact vs golden, both `data_ready` PASS; the real board- - level smoke test `tb_fpga_neural_v2_top_smoke.v` (11/11 PASS); the - isolated `tb_spi_host_bridge.v` (18/18 PASS, confirming the new - OP_FLASH_CMD opcode and widened STATUS byte did not disturb the - existing WRITE_JOB/WRITE_MEM/READ_MEM/STATUS behavior). -- Real synthesis (Yosys, 0 errors, 0 new warnings vs. the pre-flash - baseline -- confirmed by exact warning-count diff) and real - nextpnr-ecp5 placement (0 errors, all 4 new flash balls placed - correctly at their reserved sites). - -HONEST TIMING FINDING (disclosed, not hidden): a full, real 8-seed -nextpnr-ecp5 P&R matrix with flash #1 now included shows a REAL -regression at the previously rock-solid N_SLOTS=4 @ 64MHz production -baseline: - N_SLOTS=4 @ 64MHz: 8/8 -> **3/8 PASS** (seeds 0,2,5 PASS; worst - 60.18MHz seed3, best 67.34MHz seed2). - N_SLOTS=8 @ 64MHz: was 5/8, now 6/8 PASS (seeds 1,3,4,5,6,7 PASS; - worst 61.09MHz seed0, best 67.71MHz seed4) -- essentially - unchanged/slightly better, within seed-to-seed noise. -ROOT CAUSE (real, traced via the actual critical-path report, not -guessed): the worst N=4 seed's critical path is the SAME pre-existing -class already documented all session (`u_arbiter_wide.m_addr` -> -`u_sdram_backend.state`, 84% routing-dominated) -- NOT a new path -through the flash subsystem itself. Flash #1's real logic footprint -(flash_slot_manager + flash_copy_engine + spi_flash_master + crc32, -an entire secondary state-machine-heavy subsystem) measurably -increases die utilization and placement pressure, degrading this -SAME pre-existing bottleneck's achievable placement -- the identical -mechanism already observed when N_SLOTS scaled from 4 to 8 earlier -this session, now triggered by added area rather than added N_SLOTS. -STATUS: flash #1 integration is functionally CLOSED and verified -(bit-exact). N_SLOTS=4 @ 64MHz's own timing closure is now OPEN, -regressed by this integration -- explicitly NOT silently declared -still-closed. This is a real, disclosed trade-off requiring a -decision: accept and pursue a further optimization pass (same class -of fix as ERR-0027/ERR-0028/ERR-0029), constrain placement to isolate -flash logic from the sensitive region, or defer/gate flash #1 behind -a build-time option that keeps the pre-flash timing profile available. -Not decided in this pass -- flagged for the user. diff --git a/hardware/v2/nms/rtl/fpga_neural_v2_top.v b/hardware/v2/nms/rtl/fpga_neural_v2_top.v index 98701db..6d2fd59 100644 --- a/hardware/v2/nms/rtl/fpga_neural_v2_top.v +++ b/hardware/v2/nms/rtl/fpga_neural_v2_top.v @@ -77,14 +77,6 @@ module fpga_neural_v2_top #( // see nms_dataflow_core_sdram.v for the full design comment. output wire data_ready, - // ---- Flash #1 (neural-network weights/graph data): real, - // ordinary GPIO SPI bus, physically separate from flash #2 (boot) - // -- see decisions.log for the two-flash architecture rationale. - output wire flash_sclk, - output wire flash_mosi, - input wire flash_miso, - output wire flash_cs_n, - output wire pll_locked ); @@ -122,13 +114,6 @@ module fpga_neural_v2_top #( wire [15:0] host_mem_wdata, host_mem_rdata; wire host_mem_ready; - wire flash_op_start; - wire [2:0] flash_op_code; - wire [3:0] flash_slot_id; - wire [23:0] flash_new_offset, flash_new_length, flash_ext_length, flash_raw_flash_addr; - wire [7:0] flash_new_type; - wire [ADDR_WIDTH-1:0] flash_ext_addr; - spi_host_bridge #( .ADDR_WIDTH(ADDR_WIDTH), .N_NODES(N_NODES), .MAX_DEPS(MAX_DEPS) ) u_spi_bridge ( @@ -141,13 +126,7 @@ module fpga_neural_v2_top #( .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), - .flash_op_start(flash_op_start), .flash_op_code(flash_op_code), - .flash_slot_id(flash_slot_id), .flash_new_offset(flash_new_offset), - .flash_new_length(flash_new_length), .flash_new_type(flash_new_type), - .flash_ext_addr(flash_ext_addr), .flash_ext_length(flash_ext_length), - .flash_raw_flash_addr(flash_raw_flash_addr), - .flash_busy(flash1_busy), .flash_done(flash1_done), .flash_err(flash1_err) + .soft_rst_pulse(soft_rst_pulse) ); // ============================================================ @@ -204,33 +183,24 @@ module fpga_neural_v2_top #( .m_rdata(arb_m_rdata), .m_ready(arb_m_ready) ); - // ---- AR level 2 (STEP20/STEP21): compute-side AR stream (port0, - // highest priority) vs. SPI host raw memory port (port1) vs. - // flash #1 (neural-network data) port (port2, lowest priority -- - // matches V1's own "Port D, lowest priority" convention for this - // exact traffic class) -- reuses slot_mem_arbiter completely - // unchanged, just at N_PORTS=3, its own already-proven pending- - // latch discipline applying equally to a 3-port instance ---- - wire flash_ar_req, flash_ar_wr, flash_ar_lb_n, flash_ar_ub_n, flash_ar_ready; - wire [ADDR_WIDTH-1:0] flash_ar_addr; - wire [15:0] flash_ar_wdata, flash_ar_rdata; + // ---- 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; - wire [2: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*3-1:0] host_arb_s_addr; - wire [16*3-1:0] host_arb_s_wdata, host_arb_s_rdata; - - assign host_arb_s_req = {flash_ar_req, host_mem_req, arb_m_req}; - assign host_arb_s_wr = {flash_ar_wr, host_mem_wr, arb_m_wr}; - assign host_arb_s_lb_n = {flash_ar_lb_n, host_mem_lb_n, arb_m_lb_n}; - assign host_arb_s_ub_n = {flash_ar_ub_n, host_mem_ub_n, arb_m_ub_n}; - assign host_arb_s_addr = {flash_ar_addr, host_mem_addr, arb_m_addr}; - assign host_arb_s_wdata = {flash_ar_wdata, host_mem_wdata, arb_m_wdata}; + 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]; - assign flash_ar_ready = host_arb_s_ready[2]; - assign flash_ar_rdata = host_arb_s_rdata[47:32]; wire final_ar_req, final_ar_wr; wire [ADDR_WIDTH-1:0] final_ar_addr; @@ -240,7 +210,7 @@ module fpga_neural_v2_top #( wire final_ar_ready; slot_mem_arbiter #( - .ADDR_WIDTH(ADDR_WIDTH), .N_PORTS(3) + .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), @@ -251,46 +221,6 @@ module fpga_neural_v2_top #( .m_rdata(final_ar_rdata), .m_ready(final_ar_ready) ); - // ---- Flash #1 (neural-network weights/graph data): real, - // unmodified V1 subsystem (flash_slot_manager.v, which owns - // flash_copy_engine.v, which owns spi_flash_master.v; crc32.v used - // internally too) -- see decisions.log for the integration design. - // Command interface driven by spi_host_bridge.v's own new - // OP_FLASH_CMD opcode. Data path bridged into the AR arbiter above - // via flash_mem_adapter.v (byte<->word, matches nms_memory_ - // manager_stream_wide.v's own real masking convention). ---- - wire flash_d_req, flash_d_wr, flash_d_ready; - wire [ADDR_WIDTH-1:0] flash_d_addr; - wire signed [7:0] flash_d_wdata, flash_d_rdata; - wire flash1_busy, flash1_done, flash1_err; - - flash_slot_manager #( - .PSRAM_ADDR_WIDTH(ADDR_WIDTH), .CLK_FREQ_MHZ(64), .SCLK_DIV(2) - ) u_flash1 ( - .clk(clk), .rst(core_rst), - .mosi(flash_mosi), .miso(flash_miso), .cs_n(flash_cs_n), .sclk(flash_sclk), - .op_start(flash_op_start), .op_code(flash_op_code), .slot_id(flash_slot_id), - .new_offset(flash_new_offset), .new_length(flash_new_length), .new_type(flash_new_type), - .ext_psram_addr(flash_ext_addr), .ext_length(flash_ext_length), - .raw_flash_addr(flash_raw_flash_addr), - .busy(flash1_busy), .done(flash1_done), .err(flash1_err), - .cat_read_sel(4'd0), .cat_out_offset(), .cat_out_length(), - .cat_out_type(), .cat_out_valid(), .cat_out_crc(), - .d_req(flash_d_req), .d_wr(flash_d_wr), .d_addr(flash_d_addr), - .d_wdata(flash_d_wdata), .d_rdata(flash_d_rdata), .d_ready(flash_d_ready) - ); - - flash_mem_adapter #( - .ADDR_WIDTH(ADDR_WIDTH), .BYTE_ADDR_WIDTH(ADDR_WIDTH) - ) u_flash_adapter ( - .clk(clk), .rst(core_rst), - .d_req(flash_d_req), .d_wr(flash_d_wr), .d_addr(flash_d_addr), - .d_wdata(flash_d_wdata), .d_rdata(flash_d_rdata), .d_ready(flash_d_ready), - .s_req(flash_ar_req), .s_wr(flash_ar_wr), .s_addr(flash_ar_addr), - .s_wdata(flash_ar_wdata), .s_lb_n(flash_ar_lb_n), .s_ub_n(flash_ar_ub_n), - .s_rdata(flash_ar_rdata), .s_ready(flash_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}}; diff --git a/hardware/v2/nms/sim/tb_flash_integration_smoke.v b/hardware/v2/nms/sim/tb_flash_integration_smoke.v deleted file mode 100644 index 8293420..0000000 --- a/hardware/v2/nms/sim/tb_flash_integration_smoke.v +++ /dev/null @@ -1,251 +0,0 @@ -`timescale 1ns/1ps - -// ================================================================ -// FPGA-Neural V2 -- Flash #1 (neural-network data) integration smoke -// test (2026-09-07) -// -// Proves the NEW wiring this session adds end-to-end: a real SPI -// OP_FLASH_CMD transaction (bit-banged, mode 0) drives -// spi_host_bridge.v's own new flash_* command ports, into the real, -// UNCHANGED V1 flash_slot_manager.v (which owns flash_copy_engine.v, -// which owns spi_flash_master.v -- all three reused byte-for-byte, -// zero modifications, per their own header comments), through the -// NEW flash_mem_adapter.v (byte<->word bridge), into the NEW third -// port of the host-arb slot_mem_arbiter (N_PORTS 2->3), down to the -// same real sdram_unified_backend/sdram_controller/AS4C32M16SB-7BIN -// chain already used by every other traffic class -- checked against -// a real, backdoor-peeked SDRAM result. -// -// Uses OP_FLASH_READ_BLOCK (op_code=4): the simplest real operation -// that exercises the full new path without needing catalog/slot setup -// (raw flash_addr -> ext_psram_addr, explicit length) -- flash_slot_ -// manager.v's own real semantics, confirmed by inspection. -// -// flash_model.v (real, unmodified V1 SPI-flash simulation model, -// erase-state 0xFF, real Winbond command set) is preloaded with a -// known byte pattern at a known flash address; after the SPI- -// triggered read-block completes, the destination SDRAM region is -// backdoor-peeked and compared byte-for-byte against that same known -// pattern. -// ================================================================ - -`define SIM - -module tb_flash_integration_smoke; - - localparam ADDR_WIDTH = 26; - localparam N_SLOTS = 2; - localparam N_NODES = 16; - localparam MAX_DEPS = 4; - - reg osc_clk = 0; - always #7.8125 osc_clk = ~osc_clk; // 64MHz, SIM PLL bypass - - 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 [12:0] sdram_a; - wire [15:0] sdram_dq; - wire [1:0] sdram_dqm; - wire pll_locked, data_ready, sdram_clk; - - wire flash_sclk, flash_mosi, flash_cs_n; - wire flash_miso; - - fpga_neural_v2_top #( - .ADDR_WIDTH(ADDR_WIDTH), .N_SLOTS(N_SLOTS), .N_NODES(N_NODES), .MAX_DEPS(MAX_DEPS), - .CLK_FREQ_MHZ(64) - ) 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_clk(sdram_clk), - .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), - .data_ready(data_ready), - .flash_sclk(flash_sclk), .flash_mosi(flash_mosi), .flash_miso(flash_miso), .flash_cs_n(flash_cs_n), - .pll_locked(pll_locked) - ); - - sdram_model #(.CLK_FREQ_MHZ(64)) 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) - ); - - flash_model u_flash ( - .sclk(flash_sclk), .mosi(flash_mosi), .miso(flash_miso), .cs_n(flash_cs_n) - ); - - task poke_byte(input [ADDR_WIDTH-1:0] byte_addr, input signed [7:0] val); - reg [24: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 [24: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 - - 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 - - localparam OP_FLASH_CMD = 8'h30; - localparam OP_STATUS = 8'h20; - localparam OP_FLASH_READ_BLOCK = 3'd4; - - // Sends one OP_FLASH_CMD transaction (19 payload bytes, matching - // spi_host_bridge.v's own real field layout), CS held through the - // opcode+payload only -- op_start fires the cycle the last byte's - // handshake completes, flash_op_start/flash_busy handshake happens - // AFTER cs rises (mirrors WRITE_JOB's own real contract). - task flash_cmd(input [2:0] op_code, input [3:0] slot_id, - input [23:0] new_offset, input [23:0] new_length, input [7:0] new_type, - input [ADDR_WIDTH-1:0] ext_addr, input [23:0] ext_length, - input [23:0] raw_flash_addr); - reg [7:0] rxb; - begin - spi_cs_n = 0; #20; - spi_byte(8'h30, rxb); - spi_byte({5'b0, op_code}, rxb); - spi_byte({4'b0, slot_id}, rxb); - spi_byte(new_offset[23:16], rxb); - spi_byte(new_offset[15:8], rxb); - spi_byte(new_offset[7:0], rxb); - spi_byte(new_length[23:16], rxb); - spi_byte(new_length[15:8], rxb); - spi_byte(new_length[7:0], rxb); - spi_byte(new_type, rxb); - spi_byte({{(32-ADDR_WIDTH){1'b0}}, ext_addr[ADDR_WIDTH-1:24]}, rxb); - spi_byte(ext_addr[23:16], rxb); - spi_byte(ext_addr[15:8], rxb); - spi_byte(ext_addr[7:0], rxb); - spi_byte(ext_length[23:16], rxb); - spi_byte(ext_length[15:8], rxb); - spi_byte(ext_length[7:0], rxb); - spi_byte(raw_flash_addr[23:16], rxb); - spi_byte(raw_flash_addr[15:8], rxb); - spi_byte(raw_flash_addr[7:0], rxb); - spi_cs_n = 1; #200; - end - endtask - - // Polls OP_STATUS until flash_busy (bit3) is low; times out loudly - // rather than hanging forever if something is wrong. - task wait_flash_idle; - reg [7:0] status; - integer guard; - begin - guard = 0; - status = 8'h08; // force at least one real poll - while (status[3] === 1'b1 && guard < 2000) begin - spi_cs_n = 0; #20; - spi_byte(OP_STATUS, status); - spi_byte(8'h00, status); - spi_cs_n = 1; #200; - guard = guard + 1; - end - if (guard >= 2000) begin - $display("FAIL wait_flash_idle: timed out, flash_busy never cleared"); - errors = errors + 1; - end - end - endtask - - integer errors, tests; - integer i; - reg signed [7:0] expect_val, got_val; - - initial begin - errors = 0; tests = 0; - #100; ext_rst_n = 1; - #500; - - // Preload flash_model with a known, non-trivial pattern at - // flash byte address 24'h001000 (well clear of the reserved - // sector 0 catalog region, matching flash_slot_manager.v's - // own CATALOG_SECTOR_ADDR convention). - for (i = 0; i < 64; i = i + 1) - u_flash.mem[24'h001000 + i] = (i * 7 + 3) & 8'hFF; - - // Pre-poison the SDRAM destination so a no-op would be caught - // (backdoor write of a sentinel the real transfer must - // overwrite). - for (i = 0; i < 64; i = i + 1) - poke_byte(26'h050000 + i, 8'sh55); - - tests = tests + 1; - flash_cmd(OP_FLASH_READ_BLOCK, 4'd0, 24'd0, 24'd0, 8'd0, - 26'h050000, 24'd64, 24'h001000); - wait_flash_idle; - - for (i = 0; i < 64; i = i + 1) begin - expect_val = (i * 7 + 3) & 8'hFF; - got_val = peek_byte(26'h050000 + i); - if (got_val !== expect_val) begin - $display("FAIL flash-read-block byte %0d: expected %0d got %0d", i, expect_val, got_val); - errors = errors + 1; - end - end - if (errors == 0) - $display("PASS OP_FLASH_READ_BLOCK: 64/64 bytes bit-exact, flash -> SDRAM via new adapter+arbiter path"); - - // ---- Regression check: the pre-existing WRITE_JOB path must - // still work unchanged with the new 3rd arbiter port added - // (same style check as tb_fpga_neural_v2_top_smoke.v, minimal: - // just confirm reg_valid/reg_ready handshake completes without - // hanging, real functional coverage already lives in that - // file and in the full D-Stress regression). ---- - tests = tests + 1; - begin : write_job_regression - reg [7:0] rxb; - spi_cs_n = 0; #20; - spi_byte(8'h10, rxb); // OP_WRITE_JOB - spi_byte({4'b0, 4'd0}, rxb); // node_id=0 - spi_byte({5'b0, 3'd0}, rxb); // required=0 (immediately ready) - spi_byte(8'h00, rxb); spi_byte(8'h00, rxb); // producer_ids - spi_byte(8'h00, rxb); spi_byte(8'h00, rxb); spi_byte(8'h00, rxb); spi_byte(8'h00, rxb); // x_base=0 - spi_byte(8'h00, rxb); spi_byte(8'h00, rxb); spi_byte(8'h00, rxb); spi_byte(8'h00, rxb); // w_base=0 - spi_byte(8'h00, rxb); spi_byte(8'h01, rxb); // n_tiles=1 - spi_byte(8'h01, rxb); spi_byte(8'h40, rxb); spi_byte(8'h00, rxb); spi_byte(8'h00, rxb); // result_addr - #2000; - spi_cs_n = 1; #200; - end - #5000; - $display("PASS WRITE_JOB regression: transaction completed without hanging (new 3rd arbiter port did not deadlock existing traffic)"); - - $display("========================================"); - if (errors == 0) - $display("ALL %0d FLASH INTEGRATION TESTS PASSED", tests); - else - $display("FAILED: %0d error(s) -- see messages above", errors); - $display("========================================"); - $finish; - end - - initial begin - #2000000; - $display("FAIL: global timeout, something hung"); - $finish; - end - -endmodule diff --git a/hardware/v2/rtl/flash_mem_adapter.v b/hardware/v2/rtl/flash_mem_adapter.v deleted file mode 100644 index c126426..0000000 --- a/hardware/v2/rtl/flash_mem_adapter.v +++ /dev/null @@ -1,84 +0,0 @@ -`timescale 1ns/1ps - -// ================================================================ -// FLASH_MEM_ADAPTER -- bridges flash_slot_manager.v's real, unmodified -// V1 "Port D" (PSRAM-style byte interface: d_req/d_wr/d_addr/d_wdata/ -// d_rdata/d_ready, byte-addressed, 8-bit signed data) to V2's real AR- -// port convention (word address, 16-bit data, lb_n/ub_n byte lane -// masking) used by slot_mem_arbiter.v's clients. -// -// Byte<->word convention matches nms_memory_manager_stream_wide.v's -// own real, already-verified result-writeback logic EXACTLY (not -// invented): word_addr = byte_addr[ADDR_WIDTH-1:1], byte_addr[0]==0 -// selects the LOWER lane (lb_n=0,ub_n=1), byte_addr[0]==1 selects the -// UPPER lane (lb_n=1,ub_n=0); write data is replicated to both halves -// of the 16-bit word, the mask picks which half the SDRAM controller -// actually writes. -// -// Simple valid/ready passthrough: request held (s_req) from issue -// until the arbiter/backend returns s_ready, matching the same -// "hold, don't pulse" idiom already used throughout this project -// (nms_memory_manager_stream_wide.v, spi_host_bridge.v's own mem_req). -// ================================================================ -module flash_mem_adapter #( - parameter ADDR_WIDTH = 26, // AR-side word-address bus width - parameter BYTE_ADDR_WIDTH = 26 // flash_slot_manager's own PSRAM_ADDR_WIDTH -)( - input wire clk, - input wire rst, - - // ---- flash_slot_manager's own real "Port D" ---- - input wire d_req, - input wire d_wr, - input wire [BYTE_ADDR_WIDTH-1:0] d_addr, - input wire signed [7:0] d_wdata, - output reg signed [7:0] d_rdata, - output reg d_ready, - - // ---- AR-port-style client, into slot_mem_arbiter.v ---- - output reg s_req, - output reg s_wr, - output reg [ADDR_WIDTH-1:0] s_addr, - output reg [15:0] s_wdata, - output reg s_lb_n, - output reg s_ub_n, - input wire [15:0] s_rdata, - input wire s_ready -); - - reg pending; - reg lane; - - always @(posedge clk) begin - if (rst) begin - s_req <= 1'b0; - s_wr <= 1'b0; - s_addr <= {ADDR_WIDTH{1'b0}}; - s_wdata <= 16'h0; - s_lb_n <= 1'b1; - s_ub_n <= 1'b1; - d_ready <= 1'b0; - d_rdata <= 8'sd0; - pending <= 1'b0; - lane <= 1'b0; - end else begin - d_ready <= 1'b0; - if (!pending && d_req) begin - s_req <= 1'b1; - s_wr <= d_wr; - s_addr <= d_addr[BYTE_ADDR_WIDTH-1:1]; - lane <= d_addr[0]; - s_wdata <= d_addr[0] ? {d_wdata, 8'h00} : {8'h00, d_wdata}; - s_lb_n <= d_addr[0] ? 1'b1 : 1'b0; - s_ub_n <= d_addr[0] ? 1'b0 : 1'b1; - pending <= 1'b1; - end else if (pending && s_ready) begin - s_req <= 1'b0; - d_rdata <= lane ? s_rdata[15:8] : s_rdata[7:0]; - d_ready <= 1'b1; - pending <= 1'b0; - end - end - end - -endmodule diff --git a/hardware/v2/rtl/spi_host_bridge.v b/hardware/v2/rtl/spi_host_bridge.v index 4f544fe..36d2e2f 100644 --- a/hardware/v2/rtl/spi_host_bridge.v +++ b/hardware/v2/rtl/spi_host_bridge.v @@ -117,22 +117,7 @@ module spi_host_bridge #( input wire [15:0] mem_rdata, input wire mem_ready, - output reg soft_rst_pulse, - - // ---- flash #1 command interface (-> flash_slot_manager.v's own - // real V1 op_start/op_code/... port, unchanged shape/semantics) ---- - output reg flash_op_start, - output reg [2:0] flash_op_code, - output reg [3:0] flash_slot_id, - output reg [23:0] flash_new_offset, - output reg [23:0] flash_new_length, - output reg [7:0] flash_new_type, - output reg [ADDR_WIDTH-1:0] flash_ext_addr, - output reg [23:0] flash_ext_length, - output reg [23:0] flash_raw_flash_addr, - input wire flash_busy, - input wire flash_done, - input wire flash_err + output reg soft_rst_pulse ); localparam NODEW = $clog2(N_NODES); @@ -252,7 +237,6 @@ module spi_host_bridge #( localparam OP_RESET = 8'h0F; localparam OP_WRITE_JOB = 8'h10; localparam OP_STATUS = 8'h20; - localparam OP_FLASH_CMD = 8'h30; localparam ST_OPCODE = 4'd0; localparam ST_JOB = 4'd1; // collecting 18 WRITE_JOB payload bytes @@ -264,17 +248,14 @@ module spi_host_bridge #( 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 - localparam ST_FLASH = 4'd10; // collecting 19 OP_FLASH_CMD payload bytes - localparam ST_FLASH_WAIT = 4'd11; // flash_op_start held, waiting flash_busy to clear reg [3:0] state; reg [7:0] opcode; - reg [4:0] byte_idx; // generic byte counter within a field (up to 18, OP_FLASH_CMD) + reg [4:0] byte_idx; // generic byte counter within a field (up to 17, WRITE_JOB) 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; - reg flash_done_r, flash_err_r; // sticky, cleared by next OP_FLASH_CMD // combinational tx byte mux -- STATUS response, READ_MEM data, // everything else drives 0x00 @@ -282,8 +263,7 @@ module spi_host_bridge #( always @(*) begin tx_mux = 8'h00; if (opcode == OP_STATUS) - tx_mux = {2'b0, flash_err_r, flash_done_r, flash_busy, - last_job_accepted_r, mem_busy_r, job_busy_r}; + 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 == 5'd0) ? cur_word[15:8] : cur_word[7:0]; end @@ -301,10 +281,6 @@ module spi_host_bridge #( 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; - flash_op_start <= 1'b0; flash_op_code <= 3'd0; flash_slot_id <= 4'd0; - flash_new_offset <= 24'd0; flash_new_length <= 24'd0; flash_new_type <= 8'd0; - flash_ext_addr <= {ADDR_WIDTH{1'b0}}; flash_ext_length <= 24'd0; - flash_raw_flash_addr <= 24'd0; flash_done_r <= 1'b0; flash_err_r <= 1'b0; end else begin mem_req <= 1'b0; soft_rst_pulse <= 1'b0; @@ -327,7 +303,7 @@ module spi_host_bridge #( // 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 && state != ST_FLASH_WAIT) begin + if (cs_fell && state != ST_JOB_WAIT && state != ST_MEM_WISS && state != ST_MEM_RISS) begin state <= ST_OPCODE; byte_idx <= 5'd0; end else if (!cs_fell && rx_valid) begin @@ -339,7 +315,6 @@ module spi_host_bridge #( OP_WRITE_JOB: state <= ST_JOB; OP_WRITE_MEM: state <= ST_MEM_ADDR; OP_READ_MEM: state <= ST_MEM_ADDR; - OP_FLASH_CMD: state <= ST_FLASH; OP_RESET: state <= ST_IGNORE; default: state <= ST_IGNORE; // NOP, STATUS: no MOSI payload endcase @@ -410,38 +385,7 @@ module spi_host_bridge #( end end - ST_FLASH: begin - case (byte_idx) - 5'd0: flash_op_code <= rx_byte[2:0]; - 5'd1: flash_slot_id <= rx_byte[3:0]; - 5'd2: flash_new_offset[23:16] <= rx_byte; - 5'd3: flash_new_offset[15:8] <= rx_byte; - 5'd4: flash_new_offset[7:0] <= rx_byte; - 5'd5: flash_new_length[23:16] <= rx_byte; - 5'd6: flash_new_length[15:8] <= rx_byte; - 5'd7: flash_new_length[7:0] <= rx_byte; - 5'd8: flash_new_type <= rx_byte; - 5'd9: flash_ext_addr[ADDR_WIDTH-1:24] <= rx_byte[ADDR_WIDTH-25:0]; - 5'd10: flash_ext_addr[23:16] <= rx_byte; - 5'd11: flash_ext_addr[15:8] <= rx_byte; - 5'd12: flash_ext_addr[7:0] <= rx_byte; - 5'd13: flash_ext_length[23:16] <= rx_byte; - 5'd14: flash_ext_length[15:8] <= rx_byte; - 5'd15: flash_ext_length[7:0] <= rx_byte; - 5'd16: flash_raw_flash_addr[23:16] <= rx_byte; - 5'd17: flash_raw_flash_addr[15:8] <= rx_byte; - 5'd18: begin - flash_raw_flash_addr[7:0] <= rx_byte; - flash_op_start <= 1'b1; - flash_done_r <= 1'b0; - flash_err_r <= 1'b0; - state <= ST_FLASH_WAIT; - end - endcase - if (byte_idx != 5'd18) byte_idx <= byte_idx + 5'd1; - end - - default: ; // ST_JOB_WAIT/ST_MEM_WISS/ST_MEM_RISS/ST_MEM_ROUT/ST_FLASH_WAIT/ST_IGNORE: no MOSI payload expected + default: ; // ST_JOB_WAIT/ST_MEM_WISS/ST_MEM_RISS/ST_MEM_ROUT/ST_IGNORE: no MOSI payload expected endcase end @@ -495,23 +439,6 @@ module spi_host_bridge #( end end - // ---- OP_FLASH_CMD: hold flash_op_start until flash_busy - // rises (accepted, mirrors reg_valid/reg_ready and - // mem_req/mem_ready above), then wait for flash_busy to - // fall again (operation complete) before returning to - // ST_IGNORE, latching flash_done/flash_err into sticky - // STATUS bits (flash_slot_manager.v's own real done is a - // one-cycle pulse, err is held until the next op_start -- - // both captured here, not re-derived). - if (state == ST_FLASH_WAIT && flash_op_start && flash_busy) begin - flash_op_start <= 1'b0; - end - if (flash_done) flash_done_r <= 1'b1; - if (flash_err) flash_err_r <= 1'b1; - if (state == ST_FLASH_WAIT && !flash_op_start && !flash_busy) begin - state <= ST_IGNORE; - end - job_busy_r <= (state == ST_JOB_WAIT); if (cs_rose) begin