🔍 matrix_engine.matmul_overflow_test

Individual debug report — smoke regression.

← Back to regression report

matrix_engine.matmul_overflow_test

Verified
Fail signature (AssertionError)
AssertionError: [overflow_neg] 1 value mismatches

Attempt

Attempt 1 of 1 Approved
Investigation plan
  1. Searched the design spec for expected behavior (overflow_neg accumulator)
  2. Searched the design graph (overflow_neg)
  3. Searched the design spec for expected behavior (matrix_engine overflow)
  4. Searched the design graph (overflow)
  5. Searched the design graph (matrix_engine)
  6. Searched the design spec for expected behavior (matrix_engine)
  7. Inspected the module instance hierarchy (matrix_engine__R10_C10_M100)
  8. Read the RTL source (rtl/matrix_engine)
  9. Read the RTL source (rtl/matrix_engine/matrix_engine.sv)
  10. Read the RTL source (rtl/matrix_engine/matrix_engine.sv)
  11. Read the RTL source (rtl/matrix_engine/matrix_engine.sv)
  12. Read the RTL source (rtl/matrix_engine/mac_array.sv)
  13. Read the RTL source (rtl/matrix_engine/mac_array.sv)
  14. Read the RTL source (rtl/matrix_engine/pe.sv)
  15. Read the RTL source (rtl/matrix_engine/pe.sv)
Hypothesis / root causertl/matrix_engine/pe.sv:50
Confidence
94%

The accumulator saturation mux in pe.sv incorrectly returns the raw (wrapped) `sum` when a negative-operand overflow (underflow) occurs, instead of saturating to `AccumMin` (0x80000000). This is why the overflow_pos test passes (positive overflow correctly saturates to AccumMax) but overflow_neg fails: the RTL reports rtl=0x7fff05e8 (a wrapped/corrupted value) instead of the expected golden=0x80000000. The fix corrects the ternary so that when `this_add_overflows` is true and the shared operand sign is negative (acc_in[ACCUM_WIDTH-1]==1), the result saturates to AccumMin rather than passing through the overflowed `sum`.

Retrieved evidence (15 steps)
  1. Step 1: Searched the design spec for expected behavior (overflow_neg accumulator)
    (no spec sections matched 'overflow_neg accumulator')
  2. Step 2: Searched the design graph (overflow_neg)
    (no matches)
  3. Step 3: Searched the design spec for expected behavior (matrix_engine overflow)
    (no spec sections matched 'matrix_engine overflow')
  4. Step 4: Searched the design graph (overflow)
    - Port:result_overflow (id=port:mac_array__R10_C10_O8_A20.result_overflow, module=mac_array__R10_C10_O8_A20)
    - Signal:arr_result_overflow (id=signal:matrix_engine__R10_C10_M100.arr_result_overflow, module=matrix_engine__R10_C10_M100)
    - Port:arr_result_overflow (id=port:matrix_engine_ctrl__pi1.arr_result_overflow, module=matrix_engine_ctrl__pi1)
    - Port:overflow_sticky (id=port:matrix_engine__R10_C10_M100.overflow_sticky, module=matrix_engine__R10_C10_M100)
    - Port:overflow_sticky (id=port:matrix_e
  5. Step 5: Searched the design graph (matrix_engine)
    - Instance:u_matrix_engine (id=instance:tpe_top.u_matrix_engine, module=None)
    - Module:matrix_engine_ctrl__pi1 (id=module:matrix_engine_ctrl__pi1, module=None)
    - Module:matrix_engine__R10_C10_M100 (id=module:matrix_engine__R10_C10_M100, module=None)
  6. Step 6: Searched the design spec for expected behavior (matrix_engine)
    ### [docs/architecture/tpe_architecture_spec.md] 3.5 Matrix Compute Engine (`rtl/matrix_engine/`)
    Weight-stationary systolic array, default `MAC_ARRAY_ROWS` x
    `MAC_ARRAY_COLS` = 16x16 = 256 MACs, computing `C = A x B + C` over tiles up
    to `MAX_TILE_DIM` in each of M/K/N. Internally: input buffers -> MAC array
    (`rtl/matrix_engine/pe.sv` x N instances) -> accumulator
    (`rtl/matrix_engine/accumulator.sv`, int32, saturating) -> output buffer.
    
    ### [docs/architecture/tpe_architecture_spec.md] 4. Inter
  7. Step 7: Inspected the module instance hierarchy (matrix_engine__R10_C10_M100)
    {"module": {"name": "matrix_engine__R10_C10_M100", "orig_name": "matrix_engine", "level": 2, "is_top": false, "node_type": "Module", "id": "module:matrix_engine__R10_C10_M100"}, "instances": [{"instance": {"id": "instance:matrix_engine__R10_C10_M100.u_weight_buf", "name": "u_weight_buf", "parent_module": "matrix_engine__R10_C10_M100", "module_type": "dp_ram__DB10_A4_S10", "loc": "rtl/matrix_engine/matrix_engine.sv:118:5", "node_type": "Instance"}, "child": {"module": {"name": "dp_ram__DB10_A4_S1
  8. Step 8: Read the RTL source (rtl/matrix_engine)
    (denied) 'rtl/matrix_engine' does not exist
  9. Step 9: Read the RTL source (rtl/matrix_engine/matrix_engine.sv)
    --- rtl/matrix_engine/matrix_engine.sv (lines 1-50 of 283) ---
        1: // Matrix Compute Engine top level: input buffers (weight/activation/seed)
        2: // -> mac_array -> matrix_engine_ctrl -> output buffer, per
        3: // docs/architecture/tpe_architecture_spec.md section 3.5.
        4: //
        5: // The four buffers are plain rtl/common/dp_ram.sv instances, each exposing
        6: // port A externally (named <buf>_a_* to match the reusable
        7: // verif/cocotb_tb/env/SyncPortAgent convention from M1
  10. Step 10: Read the RTL source (rtl/matrix_engine/matrix_engine.sv)
    --- rtl/matrix_engine/matrix_engine.sv (lines 50-150 of 283) ---
       50: 
       51:     // seed_buf port A (TB/DMA loads C-in: one M-row per address)
       52:     input  logic                              sbuf_a_en,
       53:     input  logic                              sbuf_a_we,
       54:     input  logic [COLS*ACCUM_BYTES-1:0]       sbuf_a_strb,
       55:     input  logic [M_ADDR_WIDTH-1:0]           sbuf_a_addr,
       56:     input  logic [COLS*ACCUM_WIDTH-1:0]       sbuf_a_wdata,
       57:     output logic [CO
  11. Step 11: Read the RTL source (rtl/matrix_engine/matrix_engine.sv)
    --- rtl/matrix_engine/matrix_engine.sv (lines 200-283 of 283) ---
      200:   logic [ROWS-1:0] arr_weight_load_row;
      201:   logic signed [OPERAND_WIDTH-1:0] arr_weight_bus[COLS];
      202:   logic [ROWS-1:0] arr_a_valid_in;
      203:   logic signed [OPERAND_WIDTH-1:0] arr_a_in[ROWS];
      204:   logic signed [ACCUM_WIDTH-1:0] arr_acc_seed[COLS];
      205:   logic [COLS-1:0] arr_result_valid;
      206:   logic signed [ACCUM_WIDTH-1:0] arr_result[COLS];
      207:   logic [COLS-1:0] arr_result_overflow;
      208: 
      209:
  12. Step 12: Read the RTL source (rtl/matrix_engine/mac_array.sv)
    --- rtl/matrix_engine/mac_array.sv (lines 1-40 of 105) ---
        1: // ROWS x COLS weight-stationary systolic array of pe.sv instances.
        2: // Pure fabric: activations enter each row's west edge (a_in[r]) and flow
        3: // east; partial sums enter each column's north edge (acc_seed[c]) and flow
        4: // south; weights load into row r when weight_load_row[r] is set, taking
        5: // weight_bus[c] for column c. Getting the *timing* of a_in/acc_seed right
        6: // (the systolic skew) is matrix_
  13. Step 13: Read the RTL source (rtl/matrix_engine/mac_array.sv)
    --- rtl/matrix_engine/mac_array.sv (lines 40-105 of 105) ---
       40:   // a_grid[r][c] / valid_grid[r][c]: activation and its valid flag arriving
       41:   // at PE[r][c]'s west edge. c=0 is fed by a_in[r]/a_valid_in[r]; c=COLS
       42:   // holds the last column's a_out/valid_out (discarded except for row
       43:   // ROWS-1, which becomes result_valid).
       44:   logic signed [OPERAND_WIDTH-1:0] a_grid[ROWS][COLS+1];
       45:   logic valid_grid[ROWS][COLS+1];
       46: 
       47:   // acc_grid[r][c] / ovf_g
  14. Step 14: Read the RTL source (rtl/matrix_engine/pe.sv)
    --- rtl/matrix_engine/pe.sv (lines 1-40 of 81) ---
        1: // Systolic Processing Element (one MAC) -- the fundamental unit of
        2: // rtl/matrix_engine/mac_array.sv. Weight-stationary: the weight is loaded
        3: // once (weight_load) and held for the whole tile; activations stream east
        4: // (a_in -> a_out, one cycle of pipeline delay) and partial sums stream
        5: // south (acc_in -> acc_out, one cycle of pipeline delay), each PE adding
        6: // its own a_in*weight contribution as the 
  15. Step 15: Read the RTL source (rtl/matrix_engine/pe.sv)
    --- rtl/matrix_engine/pe.sv (lines 40-81 of 81) ---
       40:   logic signed [ACCUM_WIDTH-1:0] sum;
       41:   logic signed [ACCUM_WIDTH-1:0] sum_saturated;
       42:   logic this_add_overflows;
       43: 
       44:   assign sum = acc_in + product;
       45:   // Signed-add overflow: operands share a sign but the result doesn't.
       46:   assign this_add_overflows = (acc_in[ACCUM_WIDTH-1] == product[ACCUM_WIDTH-1]) &&
       47:                                (sum[ACCUM_WIDTH-1] != acc_in[ACCUM_WIDTH-1]);
       48:   //
Proposed patch
--- a/rtl/matrix_engine/pe.sv
+++ b/rtl/matrix_engine/pe.sv
@@ -47,7 +47,7 @@
                                (sum[ACCUM_WIDTH-1] != acc_in[ACCUM_WIDTH-1]);
   // On overflow, clamp rather than silently wrap: the shared operand sign
   // tells us which rail to saturate to.
-  assign sum_saturated = !this_add_overflows ? sum : (acc_in[ACCUM_WIDTH-1] ? sum : AccumMax);
+  assign sum_saturated = !this_add_overflows ? sum : (acc_in[ACCUM_WIDTH-1] ? AccumMin : AccumMax);
 
   always_ff @(posedge clk or negedge rst_n) begin
     if (!rst_n) begin
Answer-key check (final attempt)Correct expected bug #3 — rtl/matrix_engine/pe.sv:[50]
Rerun PASS agent-fix/matrix_engine-matmul_overflow_test-68e2f7e2
Tokens (all attempts)24,724 (~$0.0894)