8000 config_ex between a preload and its compute gives a wrong result for that matmul · Issue #411 · ucb-bar/gemmini · GitHub
[go: up one dir, main page]

Skip to content

config_ex between a preload and its compute gives a wrong result for that matmul #411

Open

Description

@youzi27

A config_ex between matmul.preload and its compute, even one that repeats the current configuration, gives a wrong C for that matmul in both WS and OS. No assertion fires. Is this sequence supposed to work? If not, an assertion or a README note would help.

Environment: Gemmini 8c3f9923 (Chipyard 1.15.0), Verilator 5.022, GemminiRocketConfig SoC with Gemmini DIM = 4 and DIM = 8 (INT8/INT32, Dataflow.BOTH). Tests built like the bareMetalC tests and run with make -C sims/verilator CONFIG=<config> LOADMEM=1 RANDOM_SEED=1 run-binary-fast BINARY=<elf>. Reference: Spike with libgemmini.

Reproducer

The test runs two back-to-back DIM x DIM matmuls with raw preload/compute commands, once as usual and once with a config_ex that repeats the current configuration between the second preload and its compute, in WS and in OS. A single matmul (gemmini_fence(); preload(...); config_ex(...); compute_preloaded(...)) also gives a wrong C.

config_between_preload_compute.c
#include <stdint.h>
#include <stdio.h>
#include "include/gemmini_testutils.h"
static elem_t A[2][DIM][DIM] __attribute__((aligned(64)));
static elem_t B[2][DIM][DIM] __attribute__((aligned(64)));
static acc_t OUT[2][DIM][DIM] __attribute__((aligned(64)));
static acc_t REF[2][DIM][DIM];

static void run(int df, int extra_cfg) {
  const uint32_t a0 = 0, a1 = DIM, b0 = 2 * DIM, b1 = 3 * DIM;
  const uint32_t c0 = 0x80000000u, c1 = 0x80000000u | DIM;    // accumulator rows 0 and DIM
  gemmini_flush(0);
  gemmini_extended3_config_ex(df, 0, 0, ACC_SCALE_IDENTITY, 1, 1, 0, 0, false);
  gemmini_extended3_config_ld(DIM * sizeof(elem_t), MVIN_SCALE_IDENTITY, false, 0);
  gemmini_extended_mvin(&A[0][0][0], a0, DIM, DIM);
  gemmini_extended_mvin(&A[1][0][0], a1, DIM, DIM);
  gemmini_extended_mvin(&B[0][0][0], b0, DIM, DIM);
  gemmini_extended_mvin(&B[1][0][0], b1, DIM, DIM);
  if (df == WEIGHT_STATIONARY) {
    gemmini_extended_preload(b0, c0, DIM, DIM, DIM, DIM);
    gemmini_extended_compute_preloaded(a0, GARBAGE_ADDR, DIM, DIM, DIM, DIM);
    gemmini_extended_preload(b1, c1, DIM, DIM, DIM, DIM);
    if (extra_cfg) gemmini_extended3_config_ex(df, 0, 0, ACC_SCALE_IDENTITY, 1, 1, 0, 0, false);
    gemmini_extended_compute_preloaded(a1, GARBAGE_ADDR, DIM, DIM, DIM, DIM);
  } else {
    gemmini_extended_preload(GARBAGE_ADDR, c0, DIM, DIM, DIM, DIM);
    gemmini_extended_compute_preloaded(a0, b0, DIM, DIM, DIM, DIM);
    gemmini_extended_preload(GARBAGE_ADDR, c1, DIM, DIM, DIM, DIM);
    if (extra_cfg) gemmini_extended3_config_ex(df, 0, 0, ACC_SCALE_IDENTITY, 1, 1, 0, 0, false);
    gemmini_extended_compute_preloaded(a1, b1, DIM, DIM, DIM, DIM);
  }
  gemmini_fence();
  gemmini_config_st(DIM * sizeof(acc_t));
  gemmini_extended_mvout((uintptr_t)&OUT[0][0][0], 0xA0000000u, DIM, DIM);
  gemmini_extended_mvout((uintptr_t)&OUT[1][0][0], 0xA0000000u | DIM, DIM, DIM);
  gemmini_fence();
  int bad[2] = {0, 0};
  for (int t = 0; t < 2; t++)
    for (int i = 0; i < DIM; i++)
      for (int j = 0; j < DIM; j++) bad[t] += OUT[t][i][j] != REF[t][i][j];
  printf("%s, extra config_ex=%d: wrong elements C0=%d C1=%d\n", df == WEIGHT_STATIONARY ? "WS" : "OS", extra_cfg, bad[0], bad[1]);
  if (bad[1])
    for (int i = 0; i < DIM; i++) {
      printf("  C1 row %d:", i);
      for (int j = 0; j < DIM; j++) printf(" %d", (int)OUT[1][i][j]);
      printf("   (C0 row %d:", i);
      for (int j = 0; j < DIM; j++) printf(" %d", (int)OUT[0][i][j]);
      printf(")\n");
    }
}

int main() {
  for (int t = 0; t < 2; t++)
    for (int i = 0; i < DIM; i++)
      for (int j = 0; j < DIM; j++) {
        A[t][i][j] = (elem_t)(((i * 5 + j * 3 + t * 7 + i * j) % 11) - 5);
        B[t][i][j] = (elem_t)(((i * 3 + j * 7 + t * 5 + 2 * i * j) % 13) - 6);
      }
  for (int t = 0; t < 2; t++)
    for (int i = 0; i < DIM; i++)
      for (int j = 0; j < DIM; j++) {
        acc_t s = 0;
        for (int k = 0; k < DIM; k++) s += (acc_t)A[t][i][k] * B[t][k][j];
        REF[t][i][j] = s;
      }
  run(WEIGHT_STATIONARY, 0);
  run(WEIGHT_STATIONARY, 1);
  run(OUTPUT_STATIONARY, 0);
  run(OUTPUT_STATIONARY, 1);
  return 0;
}
  • Expected (Spike): wrong elements C0=0 C1=0 in all four runs.
  • Observed (RTL), DIM = 4:
    WS, extra config_ex=0: wrong elements C0=0 C1=0
    WS, extra config_ex=1: wrong elements C0=0 C1=16
      C1 row 0: -24 14 0 -27   (C0 row 0: 48 -7 29 0)
      C1 row 1: -24 14 0 -27   (C0 row 1: -9 33 23 13)
      C1 row 2: -24 14 0 -27   (C0 row 2: -33 -15 -49 -18)
      C1 row 3: -24 14 0 -27   (C0 row 3: -24 14 0 -27)
    OS, extra config_ex=0: wrong elements C0=0 C1=0
    OS, extra config_ex=1: wrong elements C0=0 C1=16
      C1 row 0: 0 0 0 0   (C0 row 0: 48 -7 29 0)
      C1 row 1: 0 0 0 0   (C0 row 1: -9 33 23 13)
      C1 row 2: 0 0 0 0   (C0 row 2: -33 -15 -49 -18)
      C1 row 3: 0 0 0 0   (C0 row 3: -24 14 0 -27)
    
    DIM = 8 shows the same pattern (WS: C1=64, every row equal to the last row of C0; OS: C1=62, all zeros).
  • Controls: a fence or a CPU delay at the same place, or a config_ex between two preload/compute pairs, gives the right result.

Cause (from reading the RTL; consistent with the values above, not checked with waveforms)

  • The execute controller overlaps a compute with the following preload as soon as a third command is queued (ExecuteController.scala:600 checks cmd.valid(2)), so the config_ex lets the second preload be issued together with the first compute.
  • With a matmul in flight the config_ex cannot execute (ExecuteController.scala:541), so the controller flushes the array first (:624); the flush lands between the preload and its compute.
  • The flush advances matmul_id (MeshWithDelays.scala:114-117) but enqueues no output tag (:223, :238), so the C address of the pending preload (:219-226) is paired with the wrong rows: in WS the flush's rows are written to C1 and the compute's rows are dropped; in OS the flush flips the accumulators (ExecuteController.scala:665-667) and C1 receives the freshly preloaded zeros.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      0