Skip to main content

u280_hbm_source_sim/
main.rs

1//! CLI for bounded source-tier mock-HBM simulations.
2
3use std::env;
4use std::ffi::OsString;
5use std::path::PathBuf;
6use std::process::ExitCode;
7
8use anyhow::{Context, Result, bail, ensure};
9use u280_hbm_sim::{
10    STANDARD_SCENARIOS, Scenario, SimulationCase, run_and_write_standard_suite, run_case,
11    write_case_artifacts,
12};
13
14const USAGE: &str = "Usage: u280-hbm-source-sim --output NEW_DIR --case all|CASE [--seed U64]";
15
16struct Options {
17    output: PathBuf,
18    case: Option<Scenario>,
19    seed: Option<u64>,
20}
21
22fn main() -> ExitCode {
23    match parse_options().and_then(execute) {
24        Ok(()) => ExitCode::SUCCESS,
25        Err(error) => {
26            eprintln!("u280-hbm-source-sim: {error:#}");
27            ExitCode::FAILURE
28        }
29    }
30}
31
32fn execute(options: Options) -> Result<()> {
33    match options.case {
34        None => {
35            ensure!(options.seed.is_none(), "--seed requires one named case");
36            let reports = run_and_write_standard_suite(&options.output)?;
37            println!(
38                "completed {} source-simulation cases in {}; hardware_completion_promotable=false",
39                reports.len(),
40                options.output.display()
41            );
42        }
43        Some(scenario) => {
44            let mut case = SimulationCase::standard(scenario);
45            if let Some(seed) = options.seed {
46                case.seed = seed;
47            }
48            let artifacts = run_case(case)?;
49            write_case_artifacts(&options.output, &artifacts)?;
50            println!(
51                "completed {} in {} modeled cycles at source-simulation tier; hardware_completion_promotable=false",
52                artifacts.report.case, artifacts.report.modeled_cycles
53            );
54        }
55    }
56    Ok(())
57}
58
59fn parse_options() -> Result<Options> {
60    let mut arguments = env::args_os();
61    let _program = arguments.next();
62    let mut output = None;
63    let mut case = None;
64    let mut seed = None;
65    while let Some(argument) = arguments.next() {
66        match argument.to_str() {
67            Some("--output") => {
68                ensure!(output.is_none(), "--output was repeated");
69                output = Some(PathBuf::from(required_value(&mut arguments, "--output")?));
70            }
71            Some("--case") => {
72                ensure!(case.is_none(), "--case was repeated");
73                let value = required_value(&mut arguments, "--case")?;
74                let value = value.to_str().context("--case must be valid UTF-8")?;
75                case = Some(if value == "all" {
76                    None
77                } else {
78                    Some(Scenario::from_name(value).with_context(|| {
79                        let names = STANDARD_SCENARIOS
80                            .iter()
81                            .copied()
82                            .map(Scenario::name)
83                            .collect::<Vec<_>>()
84                            .join(", ");
85                        format!("unknown case {value}; expected all or one of: {names}")
86                    })?)
87                });
88            }
89            Some("--seed") => {
90                ensure!(seed.is_none(), "--seed was repeated");
91                let value = required_value(&mut arguments, "--seed")?;
92                let text = value.to_str().context("--seed must be valid UTF-8")?;
93                seed = Some(parse_u64(text)?);
94            }
95            Some("--help" | "-h") => bail!(USAGE),
96            Some(value) => bail!("unknown option {value}; {USAGE}"),
97            None => bail!("option name is not valid UTF-8; {USAGE}"),
98        }
99    }
100    let selected = case.with_context(|| USAGE.to_owned())?;
101    Ok(Options {
102        output: output.with_context(|| USAGE.to_owned())?,
103        case: selected,
104        seed,
105    })
106}
107
108fn parse_u64(value: &str) -> Result<u64> {
109    if let Some(hex) = value.strip_prefix("0x") {
110        ensure!(!hex.is_empty(), "hexadecimal --seed is empty");
111        u64::from_str_radix(hex, 16).context("invalid hexadecimal --seed")
112    } else {
113        ensure!(
114            !value.is_empty() && value.bytes().all(|byte| byte.is_ascii_digit()),
115            "decimal --seed must contain only digits"
116        );
117        value.parse().context("invalid decimal --seed")
118    }
119}
120
121fn required_value(
122    arguments: &mut impl Iterator<Item = OsString>,
123    option: &str,
124) -> Result<OsString> {
125    arguments
126        .next()
127        .with_context(|| format!("{option} requires a value; {USAGE}"))
128}
129
130#[cfg(test)]
131mod tests {
132    use super::parse_u64;
133
134    #[test]
135    fn seed_parser_accepts_decimal_and_prefixed_hex() {
136        assert_eq!(parse_u64("17").unwrap(), 17);
137        assert_eq!(parse_u64("0x11").unwrap(), 17);
138        assert!(parse_u64("11x").is_err());
139        assert!(parse_u64("0x").is_err());
140    }
141}