feat(v2): scaffold hardware/v1 frozen baseline + M1 Neural Processor
Begins the V2 Neural Multiprocessor / Dataflow architecture per docs/v2-description.md, per explicit user request to freeze V1 and start V2 development, copying from V1 what's needed. Scaffold: - hardware/v1/: byte-exact, read-only copy of the current V1 codebase (rtl, testbenches, tools, constraints, a representative subset of synthesis results, and reference docs) -- verified identical via diff/cmp against the live top-level tree before being made filesystem-read-only. The live top-level tree is untouched and remains the project's "production" V1 (see hardware/v1/README.md and hardware/v2/logs/decisions.log DEC-0001 for why copy-not-move). - hardware/v2/: mandatory structure (rtl/sim/constraints/synthesis/ reports/scripts/logs/docs) plus the full logging system required by the spec (development/architecture/simulation/synthesis/timing/ benchmark/decisions/experiments/errors.log). M1 -- Neural Processor (hardware/v2/rtl/neural_processor.v): - 8-stage pipelined perceptron unit (P_IN=8): input align, 8 multipliers, 3-level adder tree, accumulator, bias+activation, INT8 saturation. Genuine 1-tile/cycle throughput, not just a wider combinational datapath. - 7-state FSM (NP_IDLE..NP_ERROR per docs/v2-description.md §6, with 4 baseline states merged into NP_WAIT_OPERANDS -- see decisions.log DEC-0002); valid/ready/data/last stream interfaces per §7. - Bit-exact vs the frozen hardware/v1/rtl/neuron_parallel.v + mac8.v + mac_unit.v: 7/7 tests pass (hardware/v2/sim/tb_neural_processor.v), covering regular/mixed-sign/extreme-INT8 vectors, both activations, a zero-idle-gap back-to-back-tiles throughput check, and an 8-tile job -- verified with Verilator (see below for why). - Real synthesis + place&route (Yosys + nextpnr-ecp5): 0 CHECK problems, Fmax 183.12 MHz at ACC_WIDTH=32 (PASS at 80MHz, ~3x V1's isolated PARALLEL=8 Fmax of 61.71 MHz) and 176.21 MHz at ACC_WIDTH=24 (a user-requested comparison experiment, also bit-exact-verified; see experiments.log EXP-0001/EXP-0002 and benchmark.log). Three real bugs found and resolved during M1 development (full diagnostic record in errors.log): - Two independent, reproducible Icarus Verilog v13.0 scheduling defects (ERR-0001, ERR-0002) that silently produced wrong simulation results for standard sequential Verilog -- confirmed via Verilator 5.050 giving correct results on the same minimal repros. Verilator is now the trusted simulator for hardware/v2/ (decisions.log DEC-0004); Icarus's affected protocol-violation check was removed from the RTL and deferred architecturally to the Neural Director (DEC-0003) rather than chased further. - One real RTL bug (ERR-0003): last0 wasn't gated like valid0, letting a "last tile" tag leak into the pipeline ahead of its actual valid tile on back-to-back jobs. Fixed and verified. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013xXuuRUWZScuo1DeYJxs3v
This commit is contained in:
@@ -0,0 +1,286 @@
|
||||
`timescale 1ns/1ps
|
||||
|
||||
// ================================================================
|
||||
// FLASH_SLOT_MANAGER TESTBENCH -- F5 raw block ops (op_code 4-6:
|
||||
// OP_FLASH_READ_BLOCK / OP_FLASH_WRITE_BLOCK / OP_FLASH_ERASE),
|
||||
// straight pass-through to the internal flash_copy_engine, no
|
||||
// catalog/CRC involvement. Same real stack as flash_slot_manager_tb.v.
|
||||
//
|
||||
// TEST 1: FLASH_READ_BLOCK, byte-exact, independent oracle (data
|
||||
// planted directly in flash_model.mem[]).
|
||||
// TEST 2: FLASH_WRITE_BLOCK, byte-exact, independent oracle
|
||||
// (direct flash_model.mem[] peek after the write).
|
||||
// TEST 3: FLASH_ERASE, independent oracle (direct peek, all 0xFF).
|
||||
// TEST 4 (adversarial §A.3): FLASH_ERASE on a non-sector-aligned
|
||||
// address -> err (forwarded from flash_copy_engine's own DIR_ERASE
|
||||
// bounds check, verified reaching this module's own `err` output).
|
||||
// ================================================================
|
||||
|
||||
module tb;
|
||||
|
||||
localparam CLK_PERIOD = 12.5;
|
||||
localparam ADDR_WIDTH = 23;
|
||||
|
||||
reg clk, rst;
|
||||
initial begin clk = 1'b0; forever #(CLK_PERIOD/2.0) clk = ~clk; end
|
||||
|
||||
wire mosi, miso, cs_n, sclk_w;
|
||||
|
||||
reg op_start;
|
||||
reg [2:0] op_code;
|
||||
reg [3:0] slot_id;
|
||||
reg [23:0] new_offset, new_length;
|
||||
reg [7:0] new_type;
|
||||
reg [ADDR_WIDTH-1:0] ext_psram_addr;
|
||||
reg [23:0] ext_length;
|
||||
reg [23:0] raw_flash_addr;
|
||||
wire busy, done, err;
|
||||
|
||||
reg [3:0] cat_read_sel;
|
||||
wire [23:0] cat_out_offset, cat_out_length;
|
||||
wire [7:0] cat_out_type;
|
||||
wire cat_out_valid;
|
||||
wire [31:0] cat_out_crc;
|
||||
|
||||
localparam OP_FLASH_READ_BLOCK = 3'd4;
|
||||
localparam OP_FLASH_WRITE_BLOCK = 3'd5;
|
||||
localparam OP_FLASH_ERASE = 3'd6;
|
||||
|
||||
wire d_req, d_wr;
|
||||
wire [ADDR_WIDTH-1:0] d_addr;
|
||||
wire signed [7:0] d_wdata;
|
||||
wire signed [7:0] d_rdata;
|
||||
wire d_ready;
|
||||
|
||||
reg a_req, a_wr;
|
||||
reg [ADDR_WIDTH-1:0] a_addr;
|
||||
reg signed [7:0] a_wdata;
|
||||
wire signed [7:0] a_rdata;
|
||||
wire a_ready;
|
||||
|
||||
flash_slot_manager #(
|
||||
.PSRAM_ADDR_WIDTH(ADDR_WIDTH), .CLK_FREQ_MHZ(80), .SCLK_DIV(2),
|
||||
.CATALOG_PSRAM_ADDR(23'h000000)
|
||||
) dut (
|
||||
.clk(clk), .rst(rst),
|
||||
.mosi(mosi), .miso(miso), .cs_n(cs_n), .sclk(sclk_w),
|
||||
.op_start(op_start), .op_code(op_code), .slot_id(slot_id),
|
||||
.new_offset(new_offset), .new_length(new_length), .new_type(new_type),
|
||||
.ext_psram_addr(ext_psram_addr), .ext_length(ext_length),
|
||||
.raw_flash_addr(raw_flash_addr),
|
||||
.busy(busy), .done(done), .err(err),
|
||||
.cat_read_sel(cat_read_sel),
|
||||
.cat_out_offset(cat_out_offset), .cat_out_length(cat_out_length),
|
||||
.cat_out_type(cat_out_type), .cat_out_valid(cat_out_valid), .cat_out_crc(cat_out_crc),
|
||||
.d_req(d_req), .d_wr(d_wr), .d_addr(d_addr), .d_wdata(d_wdata),
|
||||
.d_rdata(d_rdata), .d_ready(d_ready)
|
||||
);
|
||||
|
||||
flash_model #(.DEPTH(32'h0002_0000), .TIME_SCALE(100000)) dut_flash (
|
||||
.sclk(sclk_w), .mosi(mosi), .miso(miso), .cs_n(cs_n)
|
||||
);
|
||||
|
||||
wire arb_req, arb_wr;
|
||||
wire [ADDR_WIDTH-1:0] arb_addr;
|
||||
wire signed [7:0] arb_wdata, arb_rdata;
|
||||
wire arb_ready;
|
||||
|
||||
mem_arbiter #(.ADDR_WIDTH(ADDR_WIDTH)) u_arbiter (
|
||||
.clk(clk), .rst(rst),
|
||||
.a_req(a_req), .a_wr(a_wr), .a_addr(a_addr), .a_wdata(a_wdata),
|
||||
.a_rdata(a_rdata), .a_ready(a_ready),
|
||||
.b_req(1'b0), .b_wr(1'b0), .b_addr({ADDR_WIDTH{1'b0}}), .b_wdata(8'sd0), .b_rdata(), .b_ready(),
|
||||
.c_req(1'b0), .c_wr(1'b0), .c_addr({ADDR_WIDTH{1'b0}}), .c_wdata(8'sd0), .c_rdata(), .c_ready(),
|
||||
.d_req(d_req), .d_wr(d_wr), .d_addr(d_addr), .d_wdata(d_wdata),
|
||||
.d_rdata(d_rdata), .d_ready(d_ready),
|
||||
.m_req(arb_req), .m_wr(arb_wr), .m_addr(arb_addr), .m_wdata(arb_wdata),
|
||||
.m_rdata(arb_rdata), .m_ready(arb_ready)
|
||||
);
|
||||
|
||||
wire i8_req, i8_wr;
|
||||
wire [ADDR_WIDTH-1:0] i8_addr;
|
||||
wire [15:0] i8_wdata;
|
||||
wire i8_lb_n, i8_ub_n;
|
||||
wire [15:0] i8_rdata;
|
||||
wire i8_ready;
|
||||
|
||||
int8_memory_access #(.ADDR_WIDTH(ADDR_WIDTH)) u_i8 (
|
||||
.clk(clk), .rst(rst),
|
||||
.req(arb_req), .wr(arb_wr), .addr(arb_addr), .wdata(arb_wdata),
|
||||
.rdata(arb_rdata), .ready(arb_ready),
|
||||
.mem_req(i8_req), .mem_wr(i8_wr), .mem_addr(i8_addr), .mem_wdata(i8_wdata),
|
||||
.mem_lb_n(i8_lb_n), .mem_ub_n(i8_ub_n),
|
||||
.mem_rdata(i8_rdata), .mem_ready(i8_ready)
|
||||
);
|
||||
|
||||
wire mi_req, mi_wr;
|
||||
wire [ADDR_WIDTH-1:0] mi_addr;
|
||||
wire [15:0] mi_wdata;
|
||||
wire mi_lb_n, mi_ub_n;
|
||||
wire [15:0] mi_rdata;
|
||||
wire mi_ready;
|
||||
|
||||
memory_interface #(.ADDR_WIDTH(ADDR_WIDTH), .DATA_WIDTH(16)) u_mi (
|
||||
.clk(clk), .rst(rst),
|
||||
.req(i8_req), .wr(i8_wr), .addr(i8_addr), .wdata(i8_wdata),
|
||||
.lb_n(i8_lb_n), .ub_n(i8_ub_n),
|
||||
.rdata(i8_rdata), .ready(i8_ready),
|
||||
.mem_req(mi_req), .mem_wr(mi_wr), .mem_addr(mi_addr), .mem_wdata(mi_wdata),
|
||||
.mem_lb_n(mi_lb_n), .mem_ub_n(mi_ub_n),
|
||||
.mem_rdata(mi_rdata), .mem_ready(mi_ready)
|
||||
);
|
||||
|
||||
wire [ADDR_WIDTH-1:0] psram_a;
|
||||
wire [15:0] psram_dq;
|
||||
wire psram_ce_n, psram_oe_n, psram_we_n, psram_lb_n, psram_ub_n, psram_zz_n;
|
||||
|
||||
psram_controller #(.ADDR_WIDTH(ADDR_WIDTH), .DATA_WIDTH(16), .CLK_FREQ_MHZ(80)) u_psram_ctrl (
|
||||
.clk(clk), .rst(rst),
|
||||
.mem_req(mi_req), .mem_wr(mi_wr), .mem_addr(mi_addr), .mem_wdata(mi_wdata),
|
||||
.mem_lb_n(mi_lb_n), .mem_ub_n(mi_ub_n),
|
||||
.mem_rdata(mi_rdata), .mem_ready(mi_ready),
|
||||
.psram_a(psram_a), .psram_dq(psram_dq),
|
||||
.psram_ce_n(psram_ce_n), .psram_oe_n(psram_oe_n), .psram_we_n(psram_we_n),
|
||||
.psram_lb_n(psram_lb_n), .psram_ub_n(psram_ub_n), .psram_zz_n(psram_zz_n)
|
||||
);
|
||||
|
||||
psram_model #(.ADDR_WIDTH(ADDR_WIDTH), .DATA_WIDTH(16), .DEPTH(16384)) u_psram (
|
||||
.clk(clk), .a(psram_a), .dq(psram_dq),
|
||||
.ce_n(psram_ce_n), .oe_n(psram_oe_n), .we_n(psram_we_n),
|
||||
.lb_n(psram_lb_n), .ub_n(psram_ub_n), .zz_n(psram_zz_n)
|
||||
);
|
||||
|
||||
integer errors;
|
||||
integer i;
|
||||
reg [7:0] rb;
|
||||
|
||||
task automatic do_op(
|
||||
input [2:0] p_code,
|
||||
input [23:0] p_raw_flash_addr,
|
||||
input [ADDR_WIDTH-1:0] p_ext_psram_addr,
|
||||
input [23:0] p_ext_length
|
||||
);
|
||||
integer wd;
|
||||
begin
|
||||
@(posedge clk);
|
||||
op_start <= 1'b1;
|
||||
op_code <= p_code;
|
||||
raw_flash_addr <= p_raw_flash_addr;
|
||||
ext_psram_addr <= p_ext_psram_addr;
|
||||
ext_length <= p_ext_length;
|
||||
@(posedge clk);
|
||||
op_start <= 1'b0;
|
||||
wd = 0;
|
||||
while (!done) begin
|
||||
@(posedge clk);
|
||||
wd = wd + 1;
|
||||
if (wd > 5_000_000) begin
|
||||
$display("FATAL: do_op watchdog timeout");
|
||||
$finish;
|
||||
end
|
||||
end
|
||||
end
|
||||
endtask
|
||||
|
||||
task automatic psram_read_byte(input [ADDR_WIDTH-1:0] a, output [7:0] v);
|
||||
begin
|
||||
@(posedge clk);
|
||||
a_req <= 1'b1; a_wr <= 1'b0; a_addr <= a;
|
||||
@(posedge clk);
|
||||
a_req <= 1'b0;
|
||||
while (!a_ready) @(posedge clk);
|
||||
v = a_rdata; @(posedge clk);
|
||||
end
|
||||
endtask
|
||||
|
||||
task automatic psram_write_byte(input [ADDR_WIDTH-1:0] a, input [7:0] v);
|
||||
begin
|
||||
@(posedge clk);
|
||||
a_req <= 1'b1; a_wr <= 1'b1; a_addr <= a; a_wdata <= $signed(v);
|
||||
@(posedge clk);
|
||||
a_req <= 1'b0;
|
||||
while (!a_ready) @(posedge clk);
|
||||
@(posedge clk);
|
||||
end
|
||||
endtask
|
||||
|
||||
task automatic check_byte(input [7:0] got, input [7:0] exp, input [255:0] label);
|
||||
begin
|
||||
if (got !== exp) begin
|
||||
$display("FAIL: %0s got=%02h exp=%02h", label, got, exp);
|
||||
errors = errors + 1;
|
||||
end
|
||||
end
|
||||
endtask
|
||||
|
||||
initial begin
|
||||
errors = 0;
|
||||
rst = 1'b1;
|
||||
op_start = 1'b0; op_code = 3'h0; slot_id = 4'h0;
|
||||
new_offset = 24'h0; new_length = 24'h0; new_type = 8'h0;
|
||||
ext_psram_addr = {ADDR_WIDTH{1'b0}}; ext_length = 24'h0; raw_flash_addr = 24'h0;
|
||||
cat_read_sel = 4'h0;
|
||||
a_req = 1'b0; a_wr = 1'b0; a_addr = {ADDR_WIDTH{1'b0}}; a_wdata = 8'sd0;
|
||||
|
||||
repeat (5) @(posedge clk);
|
||||
rst = 1'b0;
|
||||
repeat (5) @(posedge clk);
|
||||
|
||||
// ========================================================
|
||||
$display("--- TEST 1 starting ---");
|
||||
for (i = 0; i < 20; i = i + 1)
|
||||
dut_flash.mem[24'h00C000 + i] = 8'h90 + i[7:0];
|
||||
|
||||
do_op(OP_FLASH_READ_BLOCK, 24'h00C000, 23'h001500, 24'd20);
|
||||
if (err) begin $display("FAIL: TEST1 unexpected err"); errors = errors + 1; end
|
||||
for (i = 0; i < 20; i = i + 1) begin
|
||||
psram_read_byte(23'h001500 + i, rb);
|
||||
check_byte(rb, 8'h90 + i[7:0], "TEST1 FLASH_READ_BLOCK byte-exact");
|
||||
end
|
||||
|
||||
// ========================================================
|
||||
$display("--- TEST 2 starting ---");
|
||||
for (i = 0; i < 20; i = i + 1)
|
||||
psram_write_byte(23'h001600 + i, 8'hB0 + i[7:0]);
|
||||
|
||||
do_op(OP_FLASH_WRITE_BLOCK, 24'h00D000, 23'h001600, 24'd20);
|
||||
if (err) begin $display("FAIL: TEST2 unexpected err"); errors = errors + 1; end
|
||||
for (i = 0; i < 20; i = i + 1)
|
||||
check_byte(dut_flash.mem[24'h00D000 + i], 8'hB0 + i[7:0], "TEST2 FLASH_WRITE_BLOCK vs independent flash peek");
|
||||
|
||||
// ========================================================
|
||||
$display("--- TEST 3 starting ---");
|
||||
for (i = 0; i < 4096; i = i + 1)
|
||||
dut_flash.mem[24'h00E000 + i] = 8'h22;
|
||||
|
||||
do_op(OP_FLASH_ERASE, 24'h00E000, {ADDR_WIDTH{1'b0}}, 24'h0);
|
||||
if (err) begin $display("FAIL: TEST3 unexpected err"); errors = errors + 1; end
|
||||
for (i = 0; i < 4096; i = i + 1) begin
|
||||
if (dut_flash.mem[24'h00E000 + i] !== 8'hFF) begin
|
||||
$display("FAIL: TEST3 not erased at offset %0d", i);
|
||||
errors = errors + 1;
|
||||
i = 4096;
|
||||
end
|
||||
end
|
||||
|
||||
// ========================================================
|
||||
$display("--- TEST 4 starting ---");
|
||||
do_op(OP_FLASH_ERASE, 24'h00E100, {ADDR_WIDTH{1'b0}}, 24'h0); // not sector-aligned
|
||||
if (!err) begin $display("FAIL: TEST4 expected err for unaligned erase, got none"); errors = errors + 1; end
|
||||
|
||||
// ========================================================
|
||||
if (errors == 0)
|
||||
$display("ALL TESTS PASSED");
|
||||
else
|
||||
$display("FAILED: %0d error(s)", errors);
|
||||
|
||||
$finish;
|
||||
end
|
||||
|
||||
initial begin
|
||||
#200_000_000;
|
||||
$display("FATAL: global simulation timeout");
|
||||
$finish;
|
||||
end
|
||||
|
||||
endmodule
|
||||
Reference in New Issue
Block a user