Skip to content

emulator: add an RTL-measured matrix accumulate latency model - #120

Open
Shreyas8612 wants to merge 2 commits into
mainfrom
feat/rtl-measured-matrix-latency-profile
Open

Shreyas8612 wants to merge 2 commits into
mainfrom
feat/rtl-measured-matrix-latency-profile

Conversation

@Shreyas8612

Copy link
Copy Markdown
Collaborator

Every matrix-matrix accumulate (M_MM/M_TMM/M_BMM/M_BTMM) charged
SYSTOLIC_PROCESSING_OVERHEAD + MLEN cycles, which cannot express the
BLEN-proportional cost of the RTL matrix pipeline, so the emulator could
not be run with the measured per-op timing.

Add TRANSACTIONAL.CONFIG.MATRIX_LATENCY_MODEL ("mlen" | "rtl_blen") with
a --matrix-latency-model CLI override. "rtl_blen" charges 3 * BLEN + 11
cycles per accumulate: Verilator runs of the RTL measured 23 cycles at
BLEN=4 and 35 at BLEN=8 (accumulates are serialized, each draining
before the next starts), which fixes the slope and the intercept. The
key is optional and defaults to "mlen", so existing settings files and
cycle counts are unchanged; matrix-vector ops and write-outs keep their
charges. The option and the measurements are documented in the readme.

Verified with cargo fmt, cargo clippy --workspace --all-targets
-D warnings and cargo test --workspace: new tests pin both formulas at
BLEN=4/8 with MLEN=64, check that switching the model changes only the
accumulate charge with identical results, and cover the settings
default/override resolution. The ATen linear testbench passes on the
default path, and re-running its program with --matrix-latency-model
rtl_blen leaves the VRAM contents identical while the cycle count moves.

Every matrix-matrix accumulate (M_MM/M_TMM/M_BMM/M_BTMM) charged
SYSTOLIC_PROCESSING_OVERHEAD + MLEN cycles, which cannot express the
BLEN-proportional cost of the RTL matrix pipeline, so the emulator could
not be run with the measured per-op timing.

Add TRANSACTIONAL.CONFIG.MATRIX_LATENCY_MODEL ("mlen" | "rtl_blen") with
a --matrix-latency-model CLI override. "rtl_blen" charges 3 * BLEN + 11
cycles per accumulate: Verilator runs of the RTL measured 23 cycles at
BLEN=4 and 35 at BLEN=8 (accumulates are serialized, each draining
before the next starts), which fixes the slope and the intercept. The
key is optional and defaults to "mlen", so existing settings files and
cycle counts are unchanged; matrix-vector ops and write-outs keep their
charges. The option and the measurements are documented in the readme.

Verified with cargo fmt, cargo clippy --workspace --all-targets
-D warnings and cargo test --workspace: new tests pin both formulas at
BLEN=4/8 with MLEN=64, check that switching the model changes only the
accumulate charge with identical results, and cover the settings
default/override resolution. The ATen linear testbench passes on the
default path, and re-running its program with --matrix-latency-model
rtl_blen leaves the VRAM contents identical while the cycle count moves.
Copilot AI lite review requested due to automatic review settings September 17, 2026 01:56

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Validate invalid latency model values during configuration loading before approval.

Pull request overview

Adds configurable matrix accumulate latency models, including RTL-measured rtl_blen timing while preserving the existing default.

Changes:

  • Adds TOML and CLI configuration for mlen and rtl_blen.
  • Applies model-specific accumulate latency formulas.
  • Documents and tests both models and defaults.

Review note: Invalid settings values can panic during initialization instead of using the existing configuration fallback.

File summaries
File Reviewed change
transactional_emulator/src/runtime_config.rs Registers latency model configuration.
transactional_emulator/src/runner.rs Applies CLI override and logs the model.
transactional_emulator/src/matrix_machine.rs Charges model-specific accumulate cycles.
transactional_emulator/src/load_config.rs Parses and resolves the latency model.
transactional_emulator/src/cli.rs Adds --matrix-latency-model.
transactional_emulator/readme.md Documents models and measurements.
plena_settings.toml Sets the explicit default model.
Review details

Suppressed comments (1)

transactional_emulator/src/load_config.rs:172

  • An invalid MATRIX_LATENCY_MODEL value now panics when MATRIX_LATENCY_MODEL is first initialized, instead of being handled by the existing load_config().unwrap_or_else(...) fallback that warns and uses defaults. This makes a typo in an otherwise readable settings file abort the emulator during MatrixMachine::new; validate the enum while loading the TOML and return/handle the configuration error consistently with the other settings.
            Some(model) => model
                .value
                .parse()
                .unwrap_or_else(|err| panic!("TRANSACTIONAL.CONFIG.MATRIX_LATENCY_MODEL: {err}")),
  • Files reviewed: 7/7 changed files
  • Comments generated: 0
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +31 to +32
/// Fixed part of the RTL-measured matrix-matrix accumulate cost, in cycles.
const RTL_ACCUMULATE_FIXED_CYCLES: u32 = 11;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don’t understand why you need to do this. Why is it set to 11? Was that value obtained directly from the RTL? Plz send me the systolic-array version you’re trying to compare against?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks George. Yes, it comes directly from the RTL, and I've re-measured it on current main so it's reproducible.

Why it's needed. Every matrix-matrix accumulate in the emulator (M_MM, M_TMM, M_BMM, M_BTMM) charges SYSTOLIC_PROCESSING_OVERHEAD + MLEN cycles, with the overhead set to 0 in plena_settings.toml. On the RTL the accumulate cost doesn't depend on MLEN; it depends on BLEN. So the emulator under-charges at the RTL's default (MLEN 8: 8 vs 23 cycles) and over-charges at the emulator's default (MLEN 64: 64 vs 23). The option lets emulator timing match the RTL; the default stays mlen, so nothing changes unless you opt in.

Systolic array compared against. PLENA_RTL main @ 783ee48 (the open-source release): src/matrix_machine/rtl/matrix_machine.sv → src/basic_components/systolic_gemm_mxint/rtl/mxint_systolic_mcu.sv (KLEN = BLEN, MLEN/BLEN mini-arrays), default precision.svh, full SimTop under Verilator 5.034. Only MLEN/BLEN/HLEN/VLEN in configuration.svh were changed per geometry.

Method. DMA-free kernels through the normal rtl-sim flow (linear generator with --skip-asm-gen, then SimTop_tb.py): N back-to-back accumulates, one write-out, C_BREAK, for N = 8, 16, 24. The cost per accumulate is the slope of SimTop_tb's EXECUTION CLOCKS in N, so fixed overheads cancel. A control of N × S_ADDI_INT runs at 1 cycle per instruction, so fetch isn't the limit.

MLEN BLEN RTL cycles per accumulate 3·BLEN + 11 Emulator today (mlen)
8 4 23 23 8
16 4 23 23 16
32 4 23 23 32
64 4 23 23 64
16 8 35 35 16
32 8 35 35 32
32 16 59 59 32

M_MM, M_TMM, M_BMM and M_BTMM give identical numbers at every point, and stepping to fresh matrix/vector addresses on every accumulate changes nothing beyond the extra address instructions. So the cost is exactly linear in BLEN (slope 3, intercept 11) at three BLEN values and independent of MLEN from 8 to 64. The 11 is the measured intercept, not a tuned constant.

Emulator check. The same 24-accumulate kernel on the emulator at MLEN 64 / BLEN 4 costs 64 cycles per accumulate with mlen and 23 with this option, matching the RTL.

I've pushed 4c3ed11, which puts this provenance in the doc comments, the settings comment and the readme, and pins the BLEN 16 point in the unit test.

Reproduce
# PLENA_RTL @ 783ee48; set MLEN/BLEN/HLEN/VLEN in src/definitions/configuration.svh
# (features >= MLEN, batch a multiple of BLEN)
python -m tools.testworkloads.linear --build-dir build/test/kernel --batch 8 --in-features 32 --out-features 32
# replace build/test/kernel/generated_asm_code.asm with:
S_ADDI_INT gp1, gp0, 0
S_ADDI_INT gp2, gp0, 0
S_ADDI_INT gp3, gp0, 0
S_ADDI_INT gp4, gp0, <64 * VLEN>
M_MM 0, gp2, gp3          # repeated N times (or M_TMM / M_BMM / M_BTMM)
M_MM_WO gp4, gp0, 0       # M_BMM_WO for the batched ops
C_BREAK
python -m tools.testworkloads.linear --build-dir build/test/kernel --batch 8 --in-features 32 --out-features 32 --skip-asm-gen
python src/system/test/SimTop_tb.py --workload-dir build/test/kernel   # with the usual rtl-sim env vars
# cycles per accumulate = (EXECUTION CLOCKS at N=24 - at N=8) / 16

/// `3 * BLEN + 11`: measured on the RTL matrix pipeline with Verilator
/// (23 cycles at BLEN=4, 35 at BLEN=8).
#[value(name = "rtl_blen")]
RtlBlen,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the diff between BLEN and RtlBlen?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no second BLEN: RtlBlen was only the name of the option ("RTL-measured, linear in BLEN") and it uses the normal TRANSACTIONAL.CONFIG.BLEN. Agreed the name read like a different parameter, so in 4c3ed11 I renamed it to rtl_measured (MatrixLatencyModel::RtlMeasured). The two values are now mlen (default, SYSTOLIC_PROCESSING_OVERHEAD + MLEN) and rtl_measured (3 · BLEN + 11).

…the measurement

The option name rtl_blen read like a different BLEN. It is the
RTL-measured accumulate model and uses the normal TRANSACTIONAL.CONFIG.BLEN,
so call it rtl_measured (MatrixLatencyModel::RtlMeasured).

Re-measured on PLENA_RTL 783ee48 with Verilator 5.034 (SimTop, DMA-free
kernels of N back-to-back accumulates, cost = slope in N): 23 cycles at
BLEN=4 for MLEN 8, 16, 32 and 64, 35 at BLEN=8 for MLEN 16 and 32, and 59
at BLEN=16 for MLEN 32, identical for M_MM, M_TMM, M_BMM and M_BTMM. That
is exactly 3 * BLEN + 11 at three BLEN values, independent of MLEN. The doc
comments, settings comment and readme now cite this measurement, and the
unit test pins the BLEN=16 point.

cargo fmt --check, clippy -D warnings and cargo test --workspace pass.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants