diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c737265 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +# keep logs; ignore the heavyweight gem5 artifacts if they get huge +results/*/m5out +results/*/config.json +results/*/config.ini.bak +results/*/*.checkpoints/ +# but DO NOT ignore stats or figures: +!results/*/stats.txt +!results/*.csv +!results/*.png + +# python cache +__pycache__/ +*.pyc + diff --git a/README.md b/README.md new file mode 100644 index 0000000..3684347 --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +# SmartEdgeAI - (gem5) + +This repo holds **all scripts, commands, and logs** for Phase 3. + +## Order of operations +1) `scripts/00_env.sh` – sets env vars used by all scripts +2) `scripts/10_run_one.sh` – run a single experiment with clear args +3) `scripts/20_sweep.sh` – run the full matrix +4) `scripts/30_extract_csv.sh` – collect gem5 stats → CSV +5) `scripts/40_energy_post.py` – compute Energy/Power/**EDP=J×s** +6) `scripts/50_plot_epi.py` / `scripts/51_plot_edp_tinyml.py` – figures +7) `scripts/60_bundle_logs.sh` – bundle terminal + stats excerpts +8) (optional) `scripts/70_diff_table.py` – drowsy vs non-drowsy deltas + +## Paths assumed +- gem5 binary: `../../build/ARM/gem5.opt` +- config: `../../scripts/hetero_big_little.py` +- workloads: `../../gem5-run/{tinyml_kws,sensor_fusion,aes_ccm,attention_kernel}` + +All output is under `iot/results` and `iot/logs`. + diff --git a/logs/STATS_EXCERPTS.txt b/logs/STATS_EXCERPTS.txt new file mode 100644 index 0000000..e69de29 diff --git a/logs/TERMINAL_EXCERPTS.txt b/logs/TERMINAL_EXCERPTS.txt new file mode 100644 index 0000000..e69de29 diff --git a/results/fig_epi_across_workloads.png b/results/fig_epi_across_workloads.png new file mode 100644 index 0000000..e69de29 diff --git a/results/fig_tinyml_edp.png b/results/fig_tinyml_edp.png new file mode 100644 index 0000000..e69de29 diff --git a/results/summary.csv b/results/summary.csv new file mode 100644 index 0000000..e69de29 diff --git a/results/summary_energy.csv b/results/summary_energy.csv new file mode 100644 index 0000000..e69de29 diff --git a/scripts/00_env.sh b/scripts/00_env.sh new file mode 100644 index 0000000..7b18aaf --- /dev/null +++ b/scripts/00_env.sh @@ -0,0 +1,21 @@ +#!/bin/bash +set -eu +# source this from anywhere inside /home/carlos/projects/gem5 + +ROOT="/home/carlos/projects/gem5" +export GEM5="$ROOT/build/ARM/gem5.opt" +export CFG="$ROOT/scripts/hetero_big_little.py" +export RUN="$ROOT/gem5-run" +export OUTROOT="$ROOT/iot/results" +export LOGROOT="$ROOT/iot/logs" + +mkdir -p "$OUTROOT" "$LOGROOT" + +# record environment (append-only) +{ + echo "==== uname ===="; uname -a + echo; echo "==== date ===="; date + echo; echo "==== gem5 git ===="; (git -C "$ROOT/gem5src" rev-parse --short HEAD 2>/dev/null || echo n/a) +} >> "$LOGROOT/env.txt" +echo "[env] READY" + diff --git a/scripts/10_run_one.sh b/scripts/10_run_one.sh new file mode 100644 index 0000000..f92a830 --- /dev/null +++ b/scripts/10_run_one.sh @@ -0,0 +1,27 @@ +#!/bin/bash +set -eu +source "$(dirname "$0")/00_env.sh" + +if [[ $# -lt 5 ]]; then + echo "Usage: $0 [mem=16GB]" + exit 1 +fi + +W=$1; CORE=$2; DV=$3; DROWSY=$4; L2=$5; MEM=${6:-16GB} +OUT="$OUTROOT/${W}_${CORE}_${DV}_l2${L2}_d${DROWSY}" + +mkdir -p "$OUT" +echo "[run_one] $W $CORE $DV L2=$L2 drowsy=$DROWSY mem=$MEM -> $OUT" + +"$GEM5" "$CFG" \ + --cmd="$RUN/$W" \ + --mem="$MEM" \ + --dvfs="$DV" \ + --drowsy="$DROWSY" \ + --l2="$L2" \ + --outdir="$OUT" \ + > "$LOGROOT/${W}_${CORE}_${DV}_l2${L2}_d${DROWSY}.stdout.log" \ + 2> "$LOGROOT/${W}_${CORE}_${DV}_l2${L2}_d${DROWSY}.stderr.log" + +echo "[run_one] DONE" + diff --git a/scripts/20_sweep.sh b/scripts/20_sweep.sh new file mode 100644 index 0000000..179f490 --- /dev/null +++ b/scripts/20_sweep.sh @@ -0,0 +1,23 @@ +#!/bin/bash +set -eu +source "$(dirname "$0")/00_env.sh" + +run_case () { + local W=$1 CORE=$2 DV=$3 D=$4 L2=$5 MEM=16GB + bash "$(dirname "$0")/10_run_one.sh" "$W" "$CORE" "$DV" "$D" "$L2" "$MEM" +} + +for W in tinyml_kws sensor_fusion aes_ccm attention_kernel; do + for DV in high low; do + for D in 0 1; do + for L2 in 512kB 1MB; do + run_case "$W" big "$DV" "$D" "$L2" + run_case "$W" little "$DV" "$D" "$L2" + run_case "$W" hybrid "$DV" "$D" "$L2" + done + done + done +done + +echo "[sweep] ALL DONE" + diff --git a/scripts/30_extract_csv.sh b/scripts/30_extract_csv.sh new file mode 100644 index 0000000..802d622 --- /dev/null +++ b/scripts/30_extract_csv.sh @@ -0,0 +1,28 @@ +#!/bin/bash +set -eu +source "$(dirname "$0")/00_env.sh" + +CSV="$OUTROOT/phase3_summary.csv" +echo "workload,core,dvfs,l2,drowsy,sim_seconds,ipc,cycles,insts,l2_miss_rate" > "$CSV" + +for d in "$OUTROOT"/*; do + [[ -d "$d" ]] || continue + base=$(basename "$d") + W=$(echo "$base" | cut -d'_' -f1) + CORE=$(echo "$base" | cut -d'_' -f2) + DVFS=$(echo "$base" | cut -d'_' -f3) + L2=$(echo "$base" | sed -E 's/.*_l2([^_]+).*/\1/') + DROW=$(echo "$base" | sed -E 's/.*_d([01]).*/\1/') + S="$d/stats.txt" + + SIMS=$(awk '/^sim_seconds/ {print $2}' "$S") + IPC=$(awk '/^system\.cpu\.ipc|^system\.cpu0\.ipc/ {print $2}' "$S" | head -n1) + CYC=$(awk '/^system\.cpu\.numCycles|^system\.cpu0\.numCycles/ {print $2}' "$S" | head -n1) + INST=$(awk '/^system\.cpu\.commit\.committedInsts|^system\.cpu0\.commit\.committedInsts/ {print $2}' "$S" | head -n1) + L2MR=$(awk '/^system\.l2\.overall_miss_rate::total/ {print $2}' "$S") + + echo "$W,$CORE,$DVFS,$L2,$DROW,$SIMS,$IPC,$CYC,$INST,$L2MR" >> "$CSV" +done + +echo "[extract] wrote $CSV" + diff --git a/scripts/40_energy_post.py b/scripts/40_energy_post.py new file mode 100644 index 0000000..d83e0ab --- /dev/null +++ b/scripts/40_energy_post.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +import csv, sys, os + +root = os.path.dirname(os.path.dirname(__file__)) +src = os.path.join(root, "results", "phase3_summary.csv") +dst = os.path.join(root, "results", "phase3_summary_energy.csv") + +# === your modeling constants (document in Methods) === +EPI_PJ = {'big': 200.0, 'little': 80.0, 'hybrid': 104.0} # pJ/inst +E_MEM_PJ = 600.0 # pJ per L2 miss +DROWSY_SCALE = 0.85 # 15% energy reduction when drowsy=1 + +rows=[] +with open(src) as f: + r=csv.DictReader(f) + for row in r: + insts = float(row['insts']) + secs = float(row['sim_seconds']) + core = row['core'] + drowsy= int(row['drowsy']) + epi_pJ= EPI_PJ.get(core, EPI_PJ['little']) + + mr = float(row['l2_miss_rate']) if row['l2_miss_rate'] else 0.0 + l2_misses = mr * insts # proxy; replace with MPKI-based calc if available + + energy_instr = (epi_pJ * 1e-12) * insts + energy_mem = (E_MEM_PJ * 1e-12) * l2_misses + energy_J = energy_instr + energy_mem + if drowsy == 1: + energy_J *= DROWSY_SCALE + + power_W = energy_J / secs if secs > 0 else 0.0 + edp = energy_J * secs # CORRECT EDP + + row.update({ + 'energy_J': f"{energy_J:.6f}", + 'power_W': f"{power_W:.6f}", + 'edp': f"{edp:.6e}", + 'epi_model_pJ': f"{epi_pJ:.1f}", + }) + rows.append(row) + +with open(dst, 'w', newline='') as f: + w=csv.DictWriter(f, fieldnames=list(rows[0].keys())) + w.writeheader(); w.writerows(rows) + +print(f"[energy] wrote {dst}") + diff --git a/scripts/50_plot_epi.py b/scripts/50_plot_epi.py new file mode 100644 index 0000000..3e2155c --- /dev/null +++ b/scripts/50_plot_epi.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +import csv, os +import matplotlib.pyplot as plt +from collections import defaultdict + +root = os.path.dirname(os.path.dirname(__file__)) +src = os.path.join(root, "results", "phase3_summary_energy.csv") +out = os.path.join(root, "results", "fig_epi_across_workloads.png") + +epi_by_core = defaultdict(list) +with open(src) as f: + r=csv.DictReader(f) + for row in r: + insts=float(row['insts']); energy=float(row['energy_J']) + epi = 1e12*energy/insts if insts>0 else 0.0 + epi_by_core[row['core']].append(epi) + +cores=['big','little','hybrid'] +vals=[sum(epi_by_core[c])/max(1,len(epi_by_core[c])) for c in cores] + +plt.figure() +plt.bar(cores, vals) +plt.ylabel('EPI (pJ/inst)') +plt.title('Energy per Instruction across Workloads (avg by core mode)') +plt.tight_layout() +plt.savefig(out) +print(f"[plot] wrote {out}") + diff --git a/scripts/51_plot_edp_tinyml.py b/scripts/51_plot_edp_tinyml.py new file mode 100644 index 0000000..17dbf83 --- /dev/null +++ b/scripts/51_plot_edp_tinyml.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +import csv, os +import matplotlib.pyplot as plt + +root = os.path.dirname(os.path.dirname(__file__)) +src = os.path.join(root, "results", "phase3_summary_energy.csv") +out = os.path.join(root, "results", "fig_tinyml_edp.png") + +labels=[]; edps=[] +with open(src) as f: + r=csv.DictReader(f) + for row in r: + if row['workload']!='tinyml_kws': continue + labels.append(f"{row['core']}-{row['dvfs']}-L2{row['l2']}-d{row['drowsy']}") + edps.append(float(row['edp'])) + +plt.figure() +plt.bar(labels, edps) +plt.ylabel('EDP (J·s)') +plt.title('TinyML: EDP by configuration') +plt.xticks(rotation=60, ha='right') +plt.tight_layout() +plt.savefig(out) +print(f"[plot] wrote {out}") + diff --git a/scripts/60_bundle_logs.sh b/scripts/60_bundle_logs.sh new file mode 100644 index 0000000..4c7f2ac --- /dev/null +++ b/scripts/60_bundle_logs.sh @@ -0,0 +1,23 @@ +#!/bin/bash +set -eu +source "$(dirname "$0")/00_env.sh" + +# terminal excerpts +: > "$LOGROOT/TERMINAL_EXCERPTS.txt" +for f in "$LOGROOT"/*.stdout.log; do + echo "===== $(basename "$f") =====" >> "$LOGROOT/TERMINAL_EXCERPTS.txt" + (head -n 20 "$f"; echo "..."; tail -n 20 "$f") >> "$LOGROOT/TERMINAL_EXCERPTS.txt" + echo >> "$LOGROOT/TERMINAL_EXCERPTS.txt" +done +echo "[bundle] wrote $LOGROOT/TERMINAL_EXCERPTS.txt" + +# stats excerpts +: > "$LOGROOT/STATS_EXCERPTS.txt" +for d in "$OUTROOT"/*; do + [[ -d "$d" ]] || continue + echo "===== $(basename "$d") =====" >> "$LOGROOT/STATS_EXCERPTS.txt" + awk '/^sim_seconds|^system\.cpu\.ipc|^system\.cpu0\.ipc|^system\.cpu\.numCycles|^system\.cpu0\.numCycles|^system\.cpu\.commit\.committedInsts|^system\.cpu0\.commit\.committedInsts|^system\.l2\.overall_miss_rate::total/' "$d/stats.txt" >> "$LOGROOT/STATS_EXCERPTS.txt" + echo >> "$LOGROOT/STATS_EXCERPTS.txt" +done +echo "[bundle] wrote $LOGROOT/STATS_EXCERPTS.txt" + diff --git a/scripts/70_diff_table.py b/scripts/70_diff_table.py new file mode 100644 index 0000000..14d10bc --- /dev/null +++ b/scripts/70_diff_table.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +import csv, os +root = os.path.dirname(os.path.dirname(__file__)) +src = os.path.join(root, "results", "phase3_summary_energy.csv") +dst = os.path.join(root, "results", "phase3_drowsy_deltas.csv") + +# group by key without drowsy; compare d0 vs d1 +from collections import defaultdict +bykey = defaultdict(dict) + +with open(src) as f: + r=csv.DictReader(f) + for row in r: + key = (row['workload'], row['core'], row['dvfs'], row['l2']) + bykey[key][row['drowsy']] = row + +rows=[] +for k, d in bykey.items(): + if '0' in d and '1' in d: + a=d['0']; b=d['1'] + e0=float(a['energy_J']); e1=float(b['energy_J']) + edp0=float(a['edp']); edp1=float(b['edp']) + rows.append({ + 'workload':k[0],'core':k[1],'dvfs':k[2],'l2':k[3], + 'energy_drop_%': f"{100*(e0-e1)/e0:.2f}", + 'edp_drop_%': f"{100*(edp0-edp1)/edp0:.2f}" + }) + +with open(dst,'w',newline='') as f: + w=csv.DictWriter(f, fieldnames=list(rows[0].keys())) + w.writeheader(); w.writerows(rows) + +print(f"[delta] wrote {dst}") +