New hardware/v3/ (Artix-7 port, branch v3-artix7): the compute engine
that makes the 100x-vs-ESP32 target theoretically reachable on
XC7A100T's 240 DSP48E1 budget.
mac2_dsp_packed.v: packs 2 INT8 MACs sharing one resident weight into
a single DSP48-shaped 25x18 multiply, exploiting this project's own
weight-stationary reuse pattern (layer_weight_buffer.v, EXP-0057/0058)
where one weight is genuinely multiplied against many different
activations. Verified exhaustively: 16,777,216/16,777,216
(weight,x0,x1) combinations, 0 errors.
Two real bugs found and fixed during that verification (both purely
arithmetic/RTL, not toolchain-related):
1. An off-by-one in a declared wire width caused Verilog's part-select
unsigned-by-default rule to corrupt sign extension on the upper
product field -- ~50% of vectors failed.
2. After fixing (1), still ~50% failed: concatenating two independently
sign-extended fields ({sext(x1,9), sext(x0,16)}) is NOT equivalent
to the real arithmetic sum x1*2^16+x0 whenever the lower field is
negative (its own two's-complement encoding "bleeds" an extra 2^16
into the concatenated value). Fixed by building the packed operand
with an explicit arithmetic shift-and-add instead of concatenation.
neural_processor_packed.v: full port of hardware/v2/rtl/
neural_processor.v's pipeline (same stage count/structure), doubled on
the accumulator/bias/activation/saturation side to process two
weight-reuse positions per weight-tile stream. Verified against TWO
real hardware/v2/rtl/neural_processor.v instances (job A / job B, same
shared weight, independent activations) -- 18/18 PASS, 0 errors,
covering the functional sweep, INT8 extremes, and back-to-back jobs.
A third real bug found in the process (in the new testbench, not the
RTL): clearing operand_valid/tile_last in the same simulation delta as
the handshake edge that should register tile_last=1 races against the
DUTs' own FSM evaluation of that same edge -- the same pulse-clearing
race class found three times already today in hardware/v2/sim (EXP-0058
and its follow-up commits). Fixed the same way: hold the pulse past the
edge with a real time delay (#1) before clearing.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUG92aM9m68TRc4rG55BcC
69 lines
2.6 KiB
Verilog
69 lines
2.6 KiB
Verilog
`timescale 1ns/1ps
|
|
|
|
// ============================================================
|
|
// Exhaustive verification of mac2_dsp_packed.v's signed packing
|
|
// arithmetic: every (weight, x0, x1) combination in [-128,127]^3
|
|
// (256^3 = 16,777,216 vectors), checked against independent
|
|
// Verilog integer multiplication (the "third oracle" convention
|
|
// used throughout this project). Checks the COMBINATIONAL packed
|
|
// result directly (no per-vector clock edge) for speed -- the
|
|
// registered p0/p1 outputs are just a one-cycle pipeline of the
|
|
// same combinational value, already covered structurally by every
|
|
// other testbench in this project using this same register idiom.
|
|
// ============================================================
|
|
module tb;
|
|
localparam DATA_WIDTH = 8;
|
|
|
|
reg clk = 0;
|
|
always #5 clk = ~clk;
|
|
reg rst;
|
|
|
|
reg signed [DATA_WIDTH-1:0] weight, x0, x1;
|
|
reg valid_in;
|
|
wire signed [2*DATA_WIDTH-1:0] p0, p1;
|
|
wire valid_out;
|
|
|
|
mac2_dsp_packed #(.DATA_WIDTH(DATA_WIDTH)) dut (
|
|
.clk(clk), .rst(rst),
|
|
.weight(weight), .x0(x0), .x1(x1), .valid_in(valid_in),
|
|
.p0(p0), .p1(p1), .valid_out(valid_out)
|
|
);
|
|
|
|
integer w, a, b;
|
|
integer tests, errors;
|
|
integer exp0, exp1;
|
|
|
|
initial begin
|
|
rst = 1; weight = 0; x0 = 0; x1 = 0; valid_in = 0;
|
|
tests = 0; errors = 0;
|
|
@(posedge clk); @(posedge clk);
|
|
rst = 0;
|
|
@(posedge clk);
|
|
|
|
for (w = -128; w <= 127; w = w + 1) begin
|
|
weight = w[7:0];
|
|
for (a = -128; a <= 127; a = a + 1) begin
|
|
x0 = a[7:0];
|
|
for (b = -128; b <= 127; b = b + 1) begin
|
|
x1 = b[7:0];
|
|
#1;
|
|
tests = tests + 1;
|
|
exp0 = a * w;
|
|
exp1 = b * w;
|
|
if (dut.p0_comb !== exp0[2*DATA_WIDTH-1:0] || dut.p1_comb !== exp1[2*DATA_WIDTH-1:0]) begin
|
|
errors = errors + 1;
|
|
if (errors <= 20)
|
|
$display("FAIL w=%0d x0=%0d x1=%0d: got p0=%0d p1=%0d expected p0=%0d p1=%0d",
|
|
w, a, b, $signed(dut.p0_comb), $signed(dut.p1_comb), exp0, exp1);
|
|
end
|
|
end
|
|
end
|
|
if (w % 32 == 0) $display("... progress: weight=%0d, tests so far=%0d, errors so far=%0d", w, tests, errors);
|
|
end
|
|
|
|
$display("=== RESULT: %0d/%0d PASS, %0d errors (exhaustive weight x x0 x x1, 256^3) ===", tests-errors, tests, errors);
|
|
if (errors == 0) $display("ALL TESTS PASSED (tb_mac2_dsp_packed) -- exhaustive, mac2_dsp_packed.v is bit-exact");
|
|
$finish;
|
|
end
|
|
endmodule
|