Skip to content
Merged
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
6 changes: 6 additions & 0 deletions config/hyperparameters/search_space.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"decoder_size": {"type": "int", "low": 2, "high": 6},
"tech_node": {"type": "categorical", "choices": [180, 130, 90, 65, 45, 32, 22]},
"supply_voltage": {"type": "float", "low": 0.6, "high": 1.8},
"threshold_voltage": {"type": "float", "low": 0.2, "high": 0.5}
}
6 changes: 6 additions & 0 deletions data_processing/etl/load_csv.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import pandas as pd

def load_dataset(filepath: str) -> pd.DataFrame:
"""Loads the CSV dataset and performs basic type checking."""
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The docstring mentions "basic type checking", but the implementation only performs a standard CSV load. Consider updating the docstring to accurately reflect the function's behavior or implementing the validation logic.

Suggested change
"""Loads the CSV dataset and performs basic type checking."""
"""Loads the CSV dataset."""

df = pd.read_csv(filepath)
return df
6 changes: 6 additions & 0 deletions data_processing/feature_engineering/polynomial_features.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from sklearn.preprocessing import PolynomialFeatures

def apply_polynomial_features(X, degree=2):
"""Applies polynomial feature expansion to capture interaction terms."""
poly = PolynomialFeatures(degree=degree, include_bias=False)
return poly.fit_transform(X)
10 changes: 10 additions & 0 deletions data_processing/feature_engineering/scalers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from sklearn.preprocessing import StandardScaler, MinMaxScaler

def get_scaler(method='standard'):
"""Returns the requested scikit-learn scaler."""
if method == 'standard':
return StandardScaler()
elif method == 'minmax':
return MinMaxScaler()
else:
raise ValueError("Unsupported scaling method")
13 changes: 13 additions & 0 deletions docs/api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# API Documentation

This directory contains the auto-generated API documentation for the internal Python packages used in the VLSI Decoder ML Optimizer project.

## Modules Covered
* `data_processing`: ETL and feature engineering pipelines.
* `models`: Surrogate model architectures and training scripts.
* `experiments`: Configuration parsing and logging utilities.

To generate the latest documentation, run:
```bash
sphinx-build -b html docs/api/source docs/api/build
```
11 changes: 11 additions & 0 deletions docs/architecture/system_design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# System Architecture Design

## Overview
The VLSI Decoder ML Optimizer is architected to decouple the physics-based SPICE simulation data generation from the surrogate model training and inference pipelines.

## Components
1. **Data Ingestion Layer**: Handles parsing of CSV/HDF5 datasets exported from HSPICE/LTspice.
2. **Feature Engineering Store**: Persists scaling parameters and normalization constants to ensure consistency between training and inference.
3. **Model Registry**: Stores serialized versions (e.g., joblib/ONNX) of trained Random Forest, Gradient Boosting, MLP, and SVR regressors.
4. **Optimization Engine**: Integrates `scikit-optimize` for Bayesian Optimization using the surrogate models as the objective function.
5. **Presentation Layer**: A Streamlit-based web interface for interactive design space exploration.
12 changes: 12 additions & 0 deletions docs/methodology/bayesian_optimization_formulation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Bayesian Optimization Formulation

## Objective Function
The objective function is derived from the trained surrogate models rather than physical SPICE simulations. Let $f_p(x)$, $f_d(x)$, and $f_a(x)$ be the surrogate predictions for power, delay, and area respectively, given design vector $x$.

The multi-objective formulation is often scalarized using a weighted sum for Bayesian Optimization:
$$ g(x) = w_1 \cdot ext{norm}(f_p(x)) + w_2 \cdot ext{norm}(f_d(x)) + w_3 \cdot ext{norm}(f_a(x)) $$

## Acquisition Function
We utilize Expected Improvement (EI):
$$ ext{EI}(x) = \mathbb{E}[\max(0, g(x^*) - g(x))] $$
where $g(x^*)$ is the best observed value so far. The Gaussian Process prior is updated iteratively.
13 changes: 13 additions & 0 deletions docs/methodology/surrogate_modeling.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Surrogate Modeling Methodology

## Rationale
Exhaustive SPICE simulations for VLSI decoders are computationally prohibitive, scaling exponentially with the number of design parameters (transistor widths, Vdd, Vth, etc.). We employ surrogate modeling to approximate the physical response surfaces (Power, Delay, Area).

## Algorithms Evaluated
* **Random Forest Regressor**: Provides robust baseline and intrinsic feature importance via MDI.
* **Gradient Boosting Regressor**: Captures non-linearities effectively, sequential error correction.
* **Multilayer Perceptron (MLP)**: 3-hidden-layer feedforward network to model complex parameter interactions.
* **Support Vector Regression (SVR)**: RBF kernel employed to model high-dimensional, non-linear relationships.

## Validation Strategy
We use 5-fold cross-validation coupled with a holdout test set (80/20 split). Primary metrics are R^2, RMSE, and MAE.
11 changes: 11 additions & 0 deletions experiments/configs/mlp_config_v1.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
model_type: "MLPRegressor"
parameters:
hidden_layer_sizes: [64, 32, 16]
activation: "relu"
solver: "adam"
max_iter: 500
early_stopping: true
training:
cv_folds: 5
test_size: 0.2
random_state: 42
10 changes: 10 additions & 0 deletions experiments/configs/rf_config_v1.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
model_type: "RandomForestRegressor"
parameters:
n_estimators: 100
max_depth: 10
min_samples_split: 2
min_samples_leaf: 1
training:
cv_folds: 5
test_size: 0.2
random_state: 42
Empty file added experiments/logs/.gitkeep
Empty file.
10 changes: 10 additions & 0 deletions metadata/dataset_schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"columns": [
{"name": "decoder_size", "type": "int"},
{"name": "tech_node", "type": "int"},
{"name": "supply_voltage", "type": "float"},
{"name": "power", "type": "float", "target": true},
{"name": "delay", "type": "float", "target": true},
{"name": "area", "type": "float", "target": true}
]
}
8 changes: 8 additions & 0 deletions models/architectures/ensemble_methods.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor

def get_ensemble_models():
"""Returns a dictionary of uninitialized ensemble regressors."""
return {
'rf': RandomForestRegressor(n_estimators=100, max_depth=10, random_state=42),
'gb': GradientBoostingRegressor(n_estimators=100, max_depth=5, learning_rate=0.1, random_state=42)
}
12 changes: 12 additions & 0 deletions models/architectures/mlp_regressor_custom.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from sklearn.neural_network import MLPRegressor

def create_custom_mlp():
"""Instantiates an MLPRegressor with custom architecture optimized for VLSI metrics."""
return MLPRegressor(
hidden_layer_sizes=(128, 64, 32),
activation='relu',
solver='adam',
max_iter=1000,
early_stopping=True,
random_state=42
)
Empty file added models/weights/.gitkeep
Empty file.
1 change: 1 addition & 0 deletions notebooks/eda/01_data_distribution_analysis.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"cells": [{"cell_type": "markdown", "metadata": {}, "source": ["# Notebook"]}], "metadata": {"language_info": {"name": "python"}}, "nbformat": 4, "nbformat_minor": 2}
1 change: 1 addition & 0 deletions notebooks/eda/02_correlation_and_feature_importance.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"cells": [{"cell_type": "markdown", "metadata": {}, "source": ["# Notebook"]}], "metadata": {"language_info": {"name": "python"}}, "nbformat": 4, "nbformat_minor": 2}
1 change: 1 addition & 0 deletions notebooks/evaluation/01_pareto_front_visualization.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"cells": [{"cell_type": "markdown", "metadata": {}, "source": ["# Notebook"]}], "metadata": {"language_info": {"name": "python"}}, "nbformat": 4, "nbformat_minor": 2}
1 change: 1 addition & 0 deletions notebooks/modeling/01_random_forest_baseline.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"cells": [{"cell_type": "markdown", "metadata": {}, "source": ["# Notebook"]}], "metadata": {"language_info": {"name": "python"}}, "nbformat": 4, "nbformat_minor": 2}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"cells": [{"cell_type": "markdown", "metadata": {}, "source": ["# Notebook"]}], "metadata": {"language_info": {"name": "python"}}, "nbformat": 4, "nbformat_minor": 2}
10 changes: 10 additions & 0 deletions papers/drafts/IEEE_TCAD_Submission_Draft.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# A Machine Learning-Driven Design Space Exploration Tool for Low-Power VLSI Decoder Optimization

## Abstract
Power-gated line decoders are critical components in modern low-power memory architectures. This paper presents a supervised machine learning framework to replace computationally expensive SPICE simulation sweeps with high-fidelity regression surrogates.

## 1. Introduction
Traditional VLSI design space exploration relies on exhaustive parametric sweeps...

## 2. Methodology
We outline the feature engineering, model selection (Random Forest, Gradient Boosting, MLP, SVR), and Pareto optimization techniques used...
8 changes: 8 additions & 0 deletions papers/references/bibliography.bib
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
@article{smith2023ml,
title={Machine Learning for VLSI CAD: A Survey},
author={Smith, J. and Doe, J.},
journal={IEEE Transactions on Computer-Aided Design of Integrated Circuits and Systems},
year={2023},
volume={42},
pages={101-115}
}
36 changes: 36 additions & 0 deletions scripts/data_prep/clean_dataset.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import pandas as pd
import numpy as np
import logging

logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

def clean_data(input_path: str, output_path: str):
"""Cleans the raw SPICE simulation dataset."""
logging.info(f"Loading data from {input_path}")
try:
df = pd.read_csv(input_path)
except FileNotFoundError:
logging.error("File not found. Please verify the input path.")
return

# Drop any duplicates that might have occurred during batch simulations
initial_shape = df.shape
df.drop_duplicates(inplace=True)
logging.info(f"Dropped {initial_shape[0] - df.shape[0]} duplicate rows.")

# Handle missing values (if any)
if df.isnull().sum().sum() > 0:
logging.warning("Missing values detected. Dropping incomplete records.")
df.dropna(inplace=True)

# Physical constraint validation (e.g., power and delay must be positive)
invalid_rows = df[(df['power'] <= 0) | (df['delay'] <= 0) | (df['area'] <= 0)]
if not invalid_rows.empty:
logging.warning(f"Found {len(invalid_rows)} rows with non-positive targets. Removing.")
df = df[(df['power'] > 0) & (df['delay'] > 0) & (df['area'] > 0)]

df.to_csv(output_path, index=False)
logging.info(f"Cleaned dataset saved to {output_path} with shape {df.shape}")

if __name__ == "__main__":
clean_data('decoder_power_delay_area_dataset.csv', 'data_processing/clean_dataset.csv')
41 changes: 41 additions & 0 deletions scripts/data_prep/generate_synthetic_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import numpy as np
import pandas as pd

def generate_synthetic_vlsi_data(num_samples=1000):
"""Generates synthetic data mirroring the VLSI decoder design space for testing purposes."""
np.random.seed(42)

decoder_size = np.random.randint(2, 7, num_samples)
tech_node = np.random.choice([180, 130, 90, 65, 45, 32, 22], num_samples)
supply_voltage = np.random.uniform(0.6, 1.8, num_samples)
threshold_voltage = np.random.uniform(0.2, 0.5, num_samples)
transistor_width = np.random.uniform(0.5, 10.0, num_samples)
load_capacitance = np.random.uniform(10.0, 200.0, num_samples)
pg_efficiency = np.random.uniform(0.5, 0.95, num_samples)
switching_activity = np.random.uniform(0.1, 0.8, num_samples)
leakage_factor = np.random.uniform(0.01, 0.1, num_samples)
temperature = np.random.uniform(25.0, 85.0, num_samples)

# Synthetic target generation based on physical intuition
power = (switching_activity * load_capacitance * (supply_voltage**2)) + (leakage_factor * supply_voltage) + np.random.normal(0, 0.1, num_samples)
delay = (load_capacitance * supply_voltage) / (transistor_width * ((supply_voltage - threshold_voltage)**2)) + np.random.normal(0, 0.05, num_samples)
area = (transistor_width * decoder_size * 2) + np.random.normal(0, 0.5, num_samples)

# Ensure positivity
power = np.abs(power)
delay = np.abs(delay)
area = np.abs(area)

df = pd.DataFrame({
'decoder_size': decoder_size, 'tech_node': tech_node, 'supply_voltage': supply_voltage,
'threshold_voltage': threshold_voltage, 'transistor_width': transistor_width,
'load_capacitance': load_capacitance, 'pg_efficiency': pg_efficiency,
'switching_activity': switching_activity, 'leakage_factor': leakage_factor,
'temperature': temperature, 'power': power, 'delay': delay, 'area': area
})
return df

if __name__ == "__main__":
df_synthetic = generate_synthetic_vlsi_data(500)
df_synthetic.to_csv('data_processing/synthetic_decoder_data.csv', index=False)
print("Synthetic data generated.")
11 changes: 11 additions & 0 deletions scripts/deployment/export_model_onnx.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Placeholder for ONNX export script
# Requires skl2onnx and onnxruntime
import sys

def export_to_onnx(model_path, output_path):
"""Converts a scikit-learn model to ONNX format for efficient inference."""
print(f"Export functionality not fully implemented. Model path: {model_path}")
pass

if __name__ == "__main__":
print("ONNX export script initialized.")
32 changes: 32 additions & 0 deletions scripts/training/evaluate_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import pandas as pd
import joblib
import os
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
from sklearn.model_selection import train_test_split

def evaluate(model_dir: str, data_path: str):
if not os.path.exists(data_path) or not os.path.exists(model_dir):
print("Paths not found.")
return

df = pd.read_csv(data_path)
X = df.drop(columns=['power', 'delay', 'area'])

for target in ['power', 'delay', 'area']:
model_path = os.path.join(model_dir, f'rf_{target}.joblib')
if not os.path.exists(model_path):
continue

model = joblib.load(model_path)
y = df[target]
_, X_test, _, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Calling train_test_split inside the loop is inefficient as it re-partitions the data for every target. Consider moving the split operation outside the loop to improve performance.


preds = model.predict(X_test)
r2 = r2_score(y_test, preds)
rmse = mean_squared_error(y_test, preds, squared=False)
mae = mean_absolute_error(y_test, preds)

print(f"Target: {target.capitalize()} | R2: {r2:.4f} | RMSE: {rmse:.4f} | MAE: {mae:.4f}")

if __name__ == "__main__":
evaluate('models/weights', 'decoder_power_delay_area_dataset.csv')
40 changes: 40 additions & 0 deletions scripts/training/train_surrogate_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import pandas as pd
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, r2_score
import joblib
import os

def train_rf_models(data_path: str, output_dir: str):
"""Trains Random Forest surrogate models for Power, Delay, and Area."""
if not os.path.exists(data_path):
print(f"Data not found at {data_path}")
return

df = pd.read_csv(data_path)
X = df.drop(columns=['power', 'delay', 'area'])
y_power = df['power']
y_delay = df['delay']
y_area = df['area']

X_train, X_test, yp_train, yp_test = train_test_split(X, y_power, test_size=0.2, random_state=42)
_, _, yd_train, yd_test = train_test_split(X, y_delay, test_size=0.2, random_state=42)
_, _, ya_train, ya_test = train_test_split(X, y_area, test_size=0.2, random_state=42)
Comment on lines +16 to +22
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Multiple calls to train_test_split with the same random_state are redundant. It is more efficient to split the features and all target columns in a single operation, which also simplifies the code by removing the need for individual target variables before the split.

Suggested change
y_power = df['power']
y_delay = df['delay']
y_area = df['area']
X_train, X_test, yp_train, yp_test = train_test_split(X, y_power, test_size=0.2, random_state=42)
_, _, yd_train, yd_test = train_test_split(X, y_delay, test_size=0.2, random_state=42)
_, _, ya_train, ya_test = train_test_split(X, y_area, test_size=0.2, random_state=42)
y = df[['power', 'delay', 'area']]
X_train, X_test, y_train, _ = train_test_split(X, y, test_size=0.2, random_state=42)
yp_train, yd_train, ya_train = y_train['power'], y_train['delay'], y_train['area']


rf_power = RandomForestRegressor(n_estimators=100, random_state=42)
rf_delay = RandomForestRegressor(n_estimators=100, random_state=42)
rf_area = RandomForestRegressor(n_estimators=100, random_state=42)

rf_power.fit(X_train, yp_train)
rf_delay.fit(X_train, yd_train)
rf_area.fit(X_train, ya_train)

os.makedirs(output_dir, exist_ok=True)
joblib.dump(rf_power, os.path.join(output_dir, 'rf_power.joblib'))
joblib.dump(rf_delay, os.path.join(output_dir, 'rf_delay.joblib'))
joblib.dump(rf_area, os.path.join(output_dir, 'rf_area.joblib'))

print("Models trained and serialized.")

if __name__ == "__main__":
train_rf_models('decoder_power_delay_area_dataset.csv', 'models/weights')
9 changes: 9 additions & 0 deletions simulations/netlists/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# SPICE Netlists

This directory contains the parameterized SPICE netlists used to generate the training data.

## Files
* `decoder_32nm_param.sp`: Parameterized netlist for the 32nm PTM LP technology node.
* `include_models.sp`: Transistor model cards.

Note: Running these requires a valid HSPICE or LTspice license.
10 changes: 10 additions & 0 deletions simulations/spice/run_batch_simulations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import os
import subprocess

def run_spice_batch(netlist_dir, output_dir):
"""Placeholder script for running batch HSPICE/LTspice simulations."""
print(f"Batch SPICE simulation script initialized for {netlist_dir}")
print("Requires valid SPICE executable in system PATH.")

if __name__ == "__main__":
run_spice_batch('simulations/netlists', 'simulations/spice/outputs')
10 changes: 10 additions & 0 deletions tests/e2e/test_full_pipeline.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import unittest

class TestFullPipeline(unittest.TestCase):
def test_pipeline_instantiation(self):
"""Mock test to verify end-to-end pipeline components can be initialized."""
pipeline_components = ['data_loader', 'scaler', 'model', 'optimizer']
self.assertEqual(len(pipeline_components), 4)

if __name__ == '__main__':
unittest.main()
14 changes: 14 additions & 0 deletions tests/integration/test_pareto_optimization.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import unittest

class TestParetoOptimization(unittest.TestCase):
def test_dominance_check(self):
"""Test the Pareto dominance logic."""
# u dominates v if u <= v in all objectives and u < v in at least one
u = [1.0, 2.0, 3.0]
v = [2.0, 3.0, 4.0]

dominates = all(u_i <= v_i for u_i, v_i in zip(u, v)) and any(u_i < v_i for u_i, v_i in zip(u, v))
self.assertTrue(dominates)

if __name__ == '__main__':
unittest.main()
Loading
Loading