feat: weight_tile_gather.v, real synthesizable byte-to-tile adapter (EXP-0061)
Closes the gap EXP-0058 left testbench-only: assembling P_IN sequential layer_weight_buffer.v byte reads into one weight_data tile bus, as real RTL instead of a testbench driver task. Avoids the runtime-indexed-part-select anti-pattern already found and fixed once in neural_director.v (ERR-0027-class Fmax collapse) by using a fixed shift-concat instead. Verified in isolation against a real, unmodified layer_weight_buffer.v: 37/37 tests, 0 errors, bit-exact across sequential, back-to-back, and non-sequential/repeated (real reuse-position-style) access patterns. Full writeup in hardware/v2/logs/experiments.log EXP-0061. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MUG92aM9m68TRc4rG55BcC
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
`timescale 1ns/1ps
|
||||
|
||||
// ============================================================
|
||||
// V3 -- real synthesizable tile-gather adapter, the piece EXP-0058's
|
||||
// own log entry flagged as still missing ("a real 'tile gather
|
||||
// adapter' (8:1 byte-to-tile packer) would be the natural next M4
|
||||
// Memory Manager deliverable if this architecture is adopted for the
|
||||
// real board" -- tb_neural_processor_layer_reuse.v did this step in
|
||||
// the testbench only, not in RTL).
|
||||
//
|
||||
// Sits between layer_weight_buffer.v's byte-wide read port (one
|
||||
// address = one byte) and neural_processor_packed.v's P_IN-wide
|
||||
// weight_data tile bus. Sequences P_IN reads, one byte/cycle, and
|
||||
// assembles them via a FIXED (compile-time-constant) shift-concat --
|
||||
// deliberately NOT a runtime-indexed part-select into the wide
|
||||
// tile_data register. This project has already been bitten by that
|
||||
// exact anti-pattern twice (neural_director.v's own slot_x_base_r
|
||||
// fix, ERR-0027-class: a variable-indexed write into a wide packed
|
||||
// register synthesizes as a real hard-multiplier-fed crossbar, real
|
||||
// measured Fmax collapse 68.51->~40-47MHz) -- avoided here from the
|
||||
// start rather than found and fixed later.
|
||||
//
|
||||
// Byte read at tile_base+i lands at tile_data[i*DATA_WIDTH +:
|
||||
// DATA_WIDTH] (i=0 is the FIRST byte read, ends at the LSB end) --
|
||||
// matches neural_processor_packed.v's own w0[gi] <=
|
||||
// weight_data[gi*DATA_WIDTH +: DATA_WIDTH] indexing exactly.
|
||||
//
|
||||
// Latency: P_IN+1 cycles from tile_req to tile_valid (1 address-setup
|
||||
// cycle + P_IN capture-and-advance cycles) -- correctness-first, not
|
||||
// yet pipelined/overlapped; matches this project's own staged
|
||||
// performance-after-correctness discipline.
|
||||
// ============================================================
|
||||
module weight_tile_gather #(
|
||||
parameter DATA_WIDTH = 8,
|
||||
parameter P_IN = 8,
|
||||
parameter BUFADDRW = 7
|
||||
)(
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
|
||||
// ---- control: gather the tile starting at tile_base ----
|
||||
input wire tile_req,
|
||||
input wire [BUFADDRW-1:0] tile_base,
|
||||
output reg tile_valid, // one-cycle pulse
|
||||
output reg [DATA_WIDTH*P_IN-1:0] tile_data,
|
||||
|
||||
// ---- layer_weight_buffer.v read port ----
|
||||
output reg [BUFADDRW-1:0] rd_addr,
|
||||
input wire [DATA_WIDTH-1:0] rd_data
|
||||
);
|
||||
localparam CNTW = $clog2(P_IN+1);
|
||||
localparam G_IDLE = 1'b0, G_RUN = 1'b1;
|
||||
|
||||
reg g_state;
|
||||
reg [CNTW-1:0] byte_cnt;
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (rst) begin
|
||||
g_state <= G_IDLE;
|
||||
tile_valid <= 1'b0;
|
||||
rd_addr <= {BUFADDRW{1'b0}};
|
||||
byte_cnt <= {CNTW{1'b0}};
|
||||
tile_data <= {(DATA_WIDTH*P_IN){1'b0}};
|
||||
end else begin
|
||||
tile_valid <= 1'b0;
|
||||
case (g_state)
|
||||
G_IDLE: begin
|
||||
if (tile_req) begin
|
||||
rd_addr <= tile_base;
|
||||
byte_cnt <= {CNTW{1'b0}};
|
||||
g_state <= G_RUN;
|
||||
end
|
||||
end
|
||||
G_RUN: begin
|
||||
// rd_data reflects the rd_addr driven last cycle
|
||||
// (layer_weight_buffer.v's read is combinational).
|
||||
tile_data <= {rd_data, tile_data[DATA_WIDTH*P_IN-1:DATA_WIDTH]};
|
||||
if (byte_cnt == P_IN[CNTW-1:0] - 1'b1) begin
|
||||
tile_valid <= 1'b1;
|
||||
g_state <= G_IDLE;
|
||||
end else begin
|
||||
rd_addr <= tile_base + byte_cnt + 1'b1;
|
||||
byte_cnt <= byte_cnt + 1'b1;
|
||||
end
|
||||
end
|
||||
default: g_state <= G_IDLE;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
Reference in New Issue
Block a user