Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions plena_settings.toml
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,15 @@ dc_lib_dis = 1
[TRANSACTIONAL.CONFIG.DC_EN]
value = 1

[TRANSACTIONAL.CONFIG.MATRIX_LATENCY_MODEL]
# Cycles charged per matrix-matrix accumulate (M_MM/M_TMM/M_BMM/M_BTMM):
# "mlen" = SYSTOLIC_PROCESSING_OVERHEAD + MLEN (default)
# "rtl_measured" = 3 * BLEN + 11, measured on the PLENA_RTL matrix machine
# with Verilator (23, 35, 59 cycles at BLEN 4, 8, 16;
# independent of MLEN)
# Absent means "mlen". CLI override: --matrix-latency-model.
value = "mlen"

[TRANSACTIONAL.CONFIG.MAX_LOOP_INSTRUCTIONS]
value = 100000

Expand Down
11 changes: 11 additions & 0 deletions transactional_emulator/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,17 @@ The simulator integrates **Ramulator 2** for High-Bandwidth Memory (HBM) modelin

Writes a (BLEN, BLEN) accumulator matrix (`m_accum`) to the Vector SRAM. This operation loads a (BLEN, VLEN) matrix from HBM and uses a mask to write to the Vector SRAM.

### Accumulate latency model

`TRANSACTIONAL.CONFIG.MATRIX_LATENCY_MODEL` in `plena_settings.toml` (command-line override `--matrix-latency-model`) selects the cycles charged for each matrix-matrix accumulate (`M_MM`, `M_TMM`, `M_BMM`, `M_BTMM`):

| Value | Cycles per accumulate | Notes |
|---|---|---|
| `"mlen"` (default) | `SYSTOLIC_PROCESSING_OVERHEAD + MLEN` | The historical charge; `SYSTOLIC_PROCESSING_OVERHEAD` comes from `TRANSACTIONAL.LATENCY`. |
| `"rtl_measured"` | `3 * BLEN + 11` | Issue-to-issue cost of back-to-back accumulates on the PLENA_RTL matrix machine (`mxint_systolic_mcu`, KLEN = BLEN), measured with Verilator 5.034 on SimTop at PLENA_RTL 783ee48: 23 cycles at BLEN=4 (MLEN 8, 16, 32, 64), 35 at BLEN=8 (MLEN 16, 32) and 59 at BLEN=16 (MLEN 32), identical for `M_MM`, `M_TMM`, `M_BMM` and `M_BTMM`. The RTL drains each accumulate before starting the next and splits the MLEN-deep reduction across MLEN / BLEN parallel mini-arrays, so the cost depends on BLEN only. |

The key is optional: a settings file without it charges the `mlen` model, so existing runs are unchanged. Matrix-vector ops and write-outs are not affected by the setting.


## Notes

Expand Down
9 changes: 9 additions & 0 deletions transactional_emulator/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ use std::path::{Path, PathBuf};
pub(crate) use clap::Parser;
use clap::ValueEnum;

use crate::load_config::MatrixLatencyModel;

/// Log level filter for the tracing subscriber.
///
/// When passed via `--log-level`, this fully overrides the `RUST_LOG`
Expand Down Expand Up @@ -165,6 +167,13 @@ pub(crate) struct Opts {
/// the default ../plena_settings.toml lookup.
pub(crate) settings: Option<PathBuf>,

#[arg(long, value_enum)]
/// Override the cycles charged per matrix-matrix accumulate (M_MM, M_TMM,
/// M_BMM, M_BTMM); default: MATRIX_LATENCY_MODEL in plena_settings.toml,
/// mlen. `mlen` charges SYSTOLIC_PROCESSING_OVERHEAD + MLEN cycles;
/// `rtl_measured` charges the RTL-measured 3 * BLEN + 11 cycles.
pub(crate) matrix_latency_model: Option<MatrixLatencyModel>,

#[arg(long)]
/// Optional generated ASM source used to derive PC-to-stage labels for a
/// runtime stage profile. This is diagnostic only; normal runs omit it.
Expand Down
146 changes: 145 additions & 1 deletion transactional_emulator/src/load_config.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// load_config.rs
use serde::{Deserialize, Serialize};
use std::{env, fs, sync::LazyLock};
use std::{env, fmt, fs, str::FromStr, sync::LazyLock};

// Import the types from your main module
use quantize::{DataType, FpType, IntType, MxDataType};
Expand All @@ -15,6 +15,55 @@ pub struct ConfigValueUsize {
pub value: usize,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ConfigValueString {
pub value: String,
}

/// Cycles charged per matrix-matrix accumulate
/// (`TRANSACTIONAL.CONFIG.MATRIX_LATENCY_MODEL`).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, clap::ValueEnum)]
pub enum MatrixLatencyModel {
/// `SYSTOLIC_PROCESSING_OVERHEAD + MLEN`: the historical charge.
#[default]
Mlen,
/// `3 * BLEN + 11`: the accumulate cost measured on the PLENA_RTL matrix
/// machine with Verilator (23, 35 and 59 cycles at BLEN 4, 8 and 16; MLEN
/// does not enter). "RTL" names where the numbers come from; BLEN itself
/// is the normal `TRANSACTIONAL.CONFIG.BLEN`.
#[value(name = "rtl_measured")]
RtlMeasured,
}

impl MatrixLatencyModel {
pub const fn as_str(self) -> &'static str {
match self {
Self::Mlen => "mlen",
Self::RtlMeasured => "rtl_measured",
}
}
}

impl FromStr for MatrixLatencyModel {
type Err = String;

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.trim().to_ascii_lowercase().as_str() {
"mlen" => Ok(Self::Mlen),
"rtl_measured" => Ok(Self::RtlMeasured),
other => Err(format!(
"unsupported matrix latency model {other:?} (expected \"mlen\" or \"rtl_measured\")"
)),
}
}
}

impl fmt::Display for MatrixLatencyModel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct LatencyValue {
pub dc_lib_en: u32,
Expand Down Expand Up @@ -108,6 +157,23 @@ pub struct ConfigSection {
pub dc_en: ConfigValue,
#[serde(rename = "MAX_LOOP_INSTRUCTIONS")]
pub max_loop_instructions: ConfigValueUsize,
/// Per-accumulate latency model for matrix-matrix ops, "mlen" or
/// "rtl_measured". Optional so settings files that predate the key keep
/// working; absent means mlen.
#[serde(rename = "MATRIX_LATENCY_MODEL", default)]
pub matrix_latency_model: Option<ConfigValueString>,
}

impl ConfigSection {
pub fn matrix_latency_model(&self) -> MatrixLatencyModel {
match &self.matrix_latency_model {
None => MatrixLatencyModel::default(),
Some(model) => model
.value
.parse()
.unwrap_or_else(|err| panic!("TRANSACTIONAL.CONFIG.MATRIX_LATENCY_MODEL: {err}")),
}
}
}

#[derive(Debug, Serialize, Deserialize, Clone)]
Expand Down Expand Up @@ -181,6 +247,7 @@ impl Default for AcceleratorConfig {
hbm_v_writeback_amount: ConfigValue { value: 16 },
dc_en: ConfigValue { value: 1 },
max_loop_instructions: ConfigValueUsize { value: 10000 },
matrix_latency_model: None,
},
precision: PrecisionSection {
matrix_sram_type: MxDataTypeConfig {
Expand Down Expand Up @@ -527,6 +594,10 @@ pub fn systolic_processing_overhead() -> u32 {
get_dc_lib_value(&CONFIG.latency.systolic_processing_overhead)
}

pub fn matrix_latency_model() -> MatrixLatencyModel {
CONFIG.config.matrix_latency_model()
}

// pub fn vector_ps_cycles() -> u32 {
// get_dc_lib_value(&CONFIG.latency.vector_ps_cycles)
// }
Expand Down Expand Up @@ -702,6 +773,79 @@ mod tests {
assert_eq!(cfg.config.max_loop_instructions.value, 10000);
}

#[test]
fn test_matrix_latency_model_parses_case_insensitively() {
assert_eq!(
"mlen".parse::<MatrixLatencyModel>().unwrap(),
MatrixLatencyModel::Mlen
);
assert_eq!(
"RTL_MEASURED".parse::<MatrixLatencyModel>().unwrap(),
MatrixLatencyModel::RtlMeasured
);
assert!("blen".parse::<MatrixLatencyModel>().is_err());
assert_eq!(MatrixLatencyModel::RtlMeasured.to_string(), "rtl_measured");
}

#[test]
fn test_default_matrix_latency_model_is_mlen() {
let cfg = AcceleratorConfig::default();
assert!(cfg.config.matrix_latency_model.is_none());
assert_eq!(cfg.config.matrix_latency_model(), MatrixLatencyModel::Mlen);
}

/// Drop the `[header]` table (up to the next table) from a TOML text.
fn without_table(text: &str, header: &str) -> String {
let mut out = String::new();
let mut skipping = false;
for line in text.lines() {
if line.trim() == header {
skipping = true;
continue;
}
if skipping && line.trim_start().starts_with('[') {
skipping = false;
}
if !skipping {
out.push_str(line);
out.push('\n');
}
}
out
}

#[test]
fn test_settings_file_matrix_latency_model() {
let path = concat!(env!("CARGO_MANIFEST_DIR"), "/../plena_settings.toml");
let text = fs::read_to_string(path).unwrap();

// The checked-in value is the historical charge.
let settings: PlenaSettings = toml::from_str(&text).unwrap();
assert_eq!(
settings.transactional.config.matrix_latency_model(),
MatrixLatencyModel::Mlen
);

// A settings file without the key means the same thing.
let stripped = without_table(&text, "[TRANSACTIONAL.CONFIG.MATRIX_LATENCY_MODEL]");
assert!(!stripped.contains("MATRIX_LATENCY_MODEL"));
let settings: PlenaSettings = toml::from_str(&stripped).unwrap();
assert!(settings.transactional.config.matrix_latency_model.is_none());
assert_eq!(
settings.transactional.config.matrix_latency_model(),
MatrixLatencyModel::Mlen
);

let overridden = format!(
"{stripped}\n[TRANSACTIONAL.CONFIG.MATRIX_LATENCY_MODEL]\nvalue = \"rtl_measured\"\n"
);
let settings: PlenaSettings = toml::from_str(&overridden).unwrap();
assert_eq!(
settings.transactional.config.matrix_latency_model(),
MatrixLatencyModel::RtlMeasured
);
}

#[test]
fn test_default_precision_types_convert() {
let cfg = AcceleratorConfig::default();
Expand Down
Loading
Loading