feat: integrate flash #1 (neural-network data) RTL into V2 top-level

Closes the flash #1 RTL gap flagged in DEC-0041: real, unmodified V1
subsystem (flash_slot_manager.v/flash_copy_engine.v/spi_flash_master.v/
crc32.v) now instantiated in fpga_neural_v2_top.v, bridged to the AR
memory bus via a new flash_mem_adapter.v (byte<->word, matches
nms_memory_manager_stream_wide.v's own real masking convention), and
commandable over SPI via a new spi_host_bridge.v opcode (OP_FLASH_CMD,
0x30) using the same byte-counting idiom as OP_WRITE_JOB. Real balls
now in the LPF: flash_sclk=B2, flash_mosi=E2, flash_miso=F2,
flash_cs_n=F3.

New tb_flash_integration_smoke.v: real SPI-triggered OP_FLASH_READ_BLOCK
verified bit-exact (64/64 bytes) against a real V1 flash_model.v
instance, through the new adapter and the widened (2->3 port) host-arb
arbiter; WRITE_JOB regression confirms the new 3rd port doesn't disturb
existing traffic. Full existing regression re-run clean: D-Stress N=4/
N=8 (bit-exact + data_ready PASS), board-level smoke test (11/11),
isolated spi_host_bridge test (18/18).

Honest, disclosed finding: a full 8-seed P&R re-verification shows
N_SLOTS=4 @ 64MHz regressed from 8/8 to 3/8 PASS (worst 60.18MHz).
Root cause traced via the real critical-path report: the SAME
pre-existing arbiter-to-sdram-backend bottleneck already documented all
session, made worse by flash's added die-area placement pressure --
not a new path through the flash logic itself. N_SLOTS=8 essentially
unchanged (6/8, was 5/8). See decisions.log DEC-0042 for full detail
and open decision points.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013xXuuRUWZScuo1DeYJxs3v
This commit is contained in:
2026-09-07 18:31:50 +02:00
co-authored by Claude Sonnet 5
parent b4f33388a9
commit 59901a4905
6 changed files with 605 additions and 20 deletions
+84
View File
@@ -0,0 +1,84 @@
`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