第1章 ウェハプロセス統計的管理

Wafer Process Statistical Control and R2R Management

← シリーズ目次に戻る

📖 本章の概要

半導体製造では、ナノメートルオーダーの精密制御が求められます。本章では、 Run-to-Run(R2R)制御、Virtual Metrology(VM)、プロセスドリフト検出など、 ウェハプロセスの統計的管理とAI技術を学びます。

🎯 学習目標

⚙️ 1.1 Run-to-Run(R2R)制御の基礎

R2R制御の原理

Run-to-Run制御は、前回のウェハ(ロット)の計測結果をフィードバックし、 次のウェハの製造条件を調整する適応制御手法です。

EWMA(指数加重移動平均)制御

$$ u_k = u_{k-1} + K \cdot (T - y_k) $$

\( u_k \): 第k回の制御入力(プロセスパラメータ)
\( y_k \): 第k回の測定値
\( T \): 目標値
\( K \): 制御ゲイン(0 < K < 1)

💡 半導体プロセスでのR2R適用例
エッチング: エッチング時間調整によるCD(Critical Dimension)制御
CVD成膜: 成膜時間調整による膜厚制御
CMP: 研磨時間調整による平坦化制御
リソグラフィ: 露光量・焦点調整によるパターン精度制御

💻 コード例1.1: EWMA R2R制御システム

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy import stats
import warnings
warnings.filterwarnings('ignore')

plt.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False

class R2RController:
    """Run-to-Run EWMA制御システム"""

    def __init__(self, target, initial_input, control_gain=0.5):
        """
        Args:
            target: 目標値(例: 膜厚 100nm)
            initial_input: 初期制御入力(例: 成膜時間 60秒)
            control_gain: 制御ゲイン K(0 < K < 1)
        """
        self.target = target
        self.u = initial_input  # 現在の制御入力
        self.K = control_gain
        self.history = {
            'run': [],
            'input': [],
            'output': [],
            'error': []
        }

    def process_model(self, u, drift=0, noise_std=1.0):
        """
        プロセスモデル(簡略化)

        Args:
            u: 制御入力(成膜時間など)
            drift: プロセスドリフト
            noise_std: プロセスノイズ標準偏差

        Returns:
            測定値(膜厚など)
        """
        # 線形モデル: y = 1.5 * u + drift + noise
        gain = 1.5  # プロセスゲイン
        y = gain * u + drift + np.random.normal(0, noise_std)
        return y

    def update_control(self, measurement):
        """
        制御入力の更新(EWMA制御)

        Args:
            measurement: 測定値

        Returns:
            次回の制御入力
        """
        error = self.target - measurement

        # EWMA制御則
        u_next = self.u + self.K * error

        # 制御入力の制約(物理的制約)
        u_next = np.clip(u_next, 30, 90)  # 30-90秒の範囲

        self.u = u_next
        return u_next

    def run_simulation(self, n_runs=100, drift_start=50, drift_rate=0.1):
        """
        R2R制御シミュレーション

        Args:
            n_runs: 実行回数(ウェハ枚数)
            drift_start: ドリフト開始ラン
            drift_rate: ドリフト速度(/run)
        """
        for run in range(n_runs):
            # プロセスドリフトの計算
            if run >= drift_start:
                drift = (run - drift_start) * drift_rate
            else:
                drift = 0

            # プロセス実行と測定
            y = self.process_model(self.u, drift=drift, noise_std=2.0)

            # 履歴記録
            error = y - self.target
            self.history['run'].append(run + 1)
            self.history['input'].append(self.u)
            self.history['output'].append(y)
            self.history['error'].append(error)

            # 次回の制御入力更新
            self.update_control(y)

        return pd.DataFrame(self.history)

    def plot_results(self, df):
        """制御結果の可視化"""
        fig, axes = plt.subplots(3, 1, figsize=(14, 10))

        # 測定値のトレンド
        axes[0].plot(df['run'], df['output'], marker='o', color='#11998e',
                     linewidth=1.5, markersize=4, label='測定値')
        axes[0].axhline(y=self.target, color='red', linestyle='--',
                        linewidth=2, label=f'目標値 ({self.target}nm)')
        axes[0].fill_between(df['run'], self.target - 3, self.target + 3,
                              alpha=0.2, color='green', label='許容範囲 (±3nm)')
        axes[0].set_xlabel('ラン番号(ウェハ)')
        axes[0].set_ylabel('膜厚(nm)')
        axes[0].set_title('R2R制御による膜厚管理', fontsize=12, fontweight='bold')
        axes[0].legend()
        axes[0].grid(alpha=0.3)

        # 制御入力のトレンド
        axes[1].plot(df['run'], df['input'], marker='s', color='#38ef7d',
                     linewidth=1.5, markersize=4, label='成膜時間')
        axes[1].set_xlabel('ラン番号(ウェハ)')
        axes[1].set_ylabel('成膜時間(秒)')
        axes[1].set_title('R2R制御入力', fontsize=12, fontweight='bold')
        axes[1].legend()
        axes[1].grid(alpha=0.3)

        # 制御誤差
        axes[2].plot(df['run'], df['error'], marker='^', color='#f38181',
                     linewidth=1.5, markersize=4, label='制御誤差')
        axes[2].axhline(y=0, color='black', linestyle='-', linewidth=1)
        axes[2].fill_between(df['run'], -3, 3, alpha=0.2, color='green',
                              label='許容誤差 (±3nm)')
        axes[2].set_xlabel('ラン番号(ウェハ)')
        axes[2].set_ylabel('誤差(nm)')
        axes[2].set_title('制御誤差', fontsize=12, fontweight='bold')
        axes[2].legend()
        axes[2].grid(alpha=0.3)

        plt.tight_layout()
        plt.savefig('r2r_control_results.png', dpi=300, bbox_inches='tight')
        plt.show()

# 実行例
print("=" * 60)
print("Run-to-Run EWMA制御システム(CVD成膜プロセス)")
print("=" * 60)

# R2R制御システムの初期化
r2r = R2RController(
    target=100.0,        # 目標膜厚 100nm
    initial_input=60.0,  # 初期成膜時間 60秒
    control_gain=0.5     # 制御ゲイン K=0.5
)

# シミュレーション実行
df_results = r2r.run_simulation(
    n_runs=100,
    drift_start=50,   # 50ラン目からドリフト開始
    drift_rate=0.1    # 0.1nm/runのドリフト
)

# 性能評価
print(f"\n制御性能評価:")
print(f"平均膜厚: {df_results['output'].mean():.2f} nm")
print(f"膜厚標準偏差: {df_results['output'].std():.2f} nm")
print(f"平均絶対誤差: {df_results['error'].abs().mean():.2f} nm")
print(f"最大誤差: {df_results['error'].abs().max():.2f} nm")

# 規格内率の計算
in_spec = df_results[(df_results['output'] >= 97) & (df_results['output'] <= 103)]
print(f"規格内率 (100±3nm): {len(in_spec)/len(df_results)*100:.1f}%")

# ドリフト補正前後の比較
before_drift = df_results[df_results['run'] < 50]
after_drift = df_results[df_results['run'] >= 50]
print(f"\nドリフト発生前(1-49ラン)平均: {before_drift['output'].mean():.2f} nm")
print(f"ドリフト発生後(50-100ラン)平均: {after_drift['output'].mean():.2f} nm")

# 可視化
r2r.plot_results(df_results)

実装のポイント:

🔮 1.2 Virtual Metrology(仮想計測)

Virtual Metrologyの原理

Virtual Metrology(VM)は、プロセス装置のセンサデータ(温度、圧力、流量など)から、 計測器を使わずにウェハの品質特性(膜厚、CD、電気特性など)を予測する技術です。

💡 VMの利点
・全数計測によるリアルタイム品質管理
・計測コストと時間の削減(計測器不要)
・インライン制御の実現
・計測不可能な中間工程での品質予測

💻 コード例1.2: Random ForestによるVirtual Metrology

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import train_test_split
from sklearn.metrics import r2_score, mean_squared_error, mean_absolute_error
import warnings
warnings.filterwarnings('ignore')

plt.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False

class VirtualMetrologySystem:
    """Virtual Metrology(仮想計測)システム"""

    def __init__(self):
        self.model = None
        self.feature_names = None

    def generate_process_data(self, n_samples=500):
        """
        プロセスセンサデータの生成(エッチングプロセスを想定)

        Returns:
            (センサデータ, CD測定値)
        """
        np.random.seed(42)

        # プロセス条件(装置センサデータ)
        rf_power = np.random.normal(1000, 50, n_samples)      # RFパワー(W)
        pressure = np.random.normal(50, 5, n_samples)          # 圧力(mTorr)
        gas_flow_ar = np.random.normal(200, 10, n_samples)    # Arガス流量(sccm)
        gas_flow_cf4 = np.random.normal(50, 5, n_samples)     # CF4ガス流量(sccm)
        temp_chamber = np.random.normal(60, 3, n_samples)     # チャンバー温度(℃)
        etch_time = np.random.normal(120, 5, n_samples)       # エッチング時間(秒)

        # CD(Critical Dimension)の物理モデル
        # CD = f(RF power, pressure, gas flows, temp, time) + noise
        cd_base = 90  # ベースCD(nm)

        cd = (cd_base
              - 0.002 * (rf_power - 1000)      # RFパワー↑ → CD↓
              + 0.05 * (pressure - 50)          # 圧力↑ → CD↑
              - 0.01 * (gas_flow_cf4 - 50)     # CF4↑ → CD↓(エッチング促進)
              + 0.005 * (temp_chamber - 60)    # 温度↑ → CD↑
              - 0.03 * (etch_time - 120)       # 時間↑ → CD↓
              + np.random.normal(0, 1, n_samples))  # ノイズ

        # データフレーム作成
        df = pd.DataFrame({
            'rf_power': rf_power,
            'pressure': pressure,
            'gas_flow_ar': gas_flow_ar,
            'gas_flow_cf4': gas_flow_cf4,
            'temp_chamber': temp_chamber,
            'etch_time': etch_time,
            'cd_measured': cd
        })

        return df

    def train_vm_model(self, df, feature_columns, target_column='cd_measured'):
        """
        VMモデルの訓練

        Args:
            df: プロセスデータ
            feature_columns: 特徴量カラム
            target_column: 予測対象カラム
        """
        X = df[feature_columns].values
        y = df[target_column].values

        # 訓練/テストデータ分割
        X_train, X_test, y_train, y_test = train_test_split(
            X, y, test_size=0.3, random_state=42
        )

        # Random Forestモデル
        self.model = RandomForestRegressor(
            n_estimators=100,
            max_depth=15,
            min_samples_split=5,
            random_state=42
        )
        self.model.fit(X_train, y_train)
        self.feature_names = feature_columns

        # 予測と評価
        y_pred_train = self.model.predict(X_train)
        y_pred_test = self.model.predict(X_test)

        # 性能指標
        r2_train = r2_score(y_train, y_pred_train)
        r2_test = r2_score(y_test, y_pred_test)
        rmse_test = np.sqrt(mean_squared_error(y_test, y_pred_test))
        mae_test = mean_absolute_error(y_test, y_pred_test)

        results = {
            'X_train': X_train, 'y_train': y_train,
            'X_test': X_test, 'y_test': y_test,
            'y_pred_train': y_pred_train, 'y_pred_test': y_pred_test,
            'r2_train': r2_train, 'r2_test': r2_test,
            'rmse_test': rmse_test, 'mae_test': mae_test
        }

        return results

    def predict_cd(self, process_conditions):
        """
        プロセス条件からCDを予測

        Args:
            process_conditions: プロセス条件の配列またはDataFrame

        Returns:
            予測CD値
        """
        if self.model is None:
            raise ValueError("モデルが訓練されていません")

        return self.model.predict(process_conditions)

    def plot_vm_results(self, results):
        """VM結果の可視化"""
        fig, axes = plt.subplots(2, 2, figsize=(14, 10))

        # 予測精度プロット(訓練データ)
        axes[0, 0].scatter(results['y_train'], results['y_pred_train'],
                           alpha=0.5, s=20, color='#11998e', label='訓練データ')
        axes[0, 0].plot([85, 95], [85, 95], 'r--', linewidth=2, label='理想直線')
        axes[0, 0].set_xlabel('実測CD(nm)')
        axes[0, 0].set_ylabel('予測CD(nm)')
        axes[0, 0].set_title(f'訓練データ予測精度 (R²={results["r2_train"]:.4f})',
                             fontsize=12, fontweight='bold')
        axes[0, 0].legend()
        axes[0, 0].grid(alpha=0.3)

        # 予測精度プロット(テストデータ)
        axes[0, 1].scatter(results['y_test'], results['y_pred_test'],
                           alpha=0.5, s=20, color='#38ef7d', label='テストデータ')
        axes[0, 1].plot([85, 95], [85, 95], 'r--', linewidth=2, label='理想直線')
        axes[0, 1].set_xlabel('実測CD(nm)')
        axes[0, 1].set_ylabel('予測CD(nm)')
        axes[0, 1].set_title(f'テストデータ予測精度 (R²={results["r2_test"]:.4f})',
                             fontsize=12, fontweight='bold')
        axes[0, 1].legend()
        axes[0, 1].grid(alpha=0.3)

        # 予測誤差の分布
        errors = results['y_pred_test'] - results['y_test']
        axes[1, 0].hist(errors, bins=30, color='#4ecdc4', alpha=0.7, edgecolor='black')
        axes[1, 0].axvline(x=0, color='red', linestyle='--', linewidth=2, label='誤差ゼロ')
        axes[1, 0].set_xlabel('予測誤差(nm)')
        axes[1, 0].set_ylabel('頻度')
        axes[1, 0].set_title(f'予測誤差分布 (MAE={results["mae_test"]:.2f}nm)',
                             fontsize=12, fontweight='bold')
        axes[1, 0].legend()
        axes[1, 0].grid(alpha=0.3)

        # 特徴量重要度
        feature_importance = pd.DataFrame({
            'feature': self.feature_names,
            'importance': self.model.feature_importances_
        }).sort_values('importance', ascending=True)

        axes[1, 1].barh(feature_importance['feature'], feature_importance['importance'],
                        color='#f38181', alpha=0.8)
        axes[1, 1].set_xlabel('重要度')
        axes[1, 1].set_title('特徴量重要度', fontsize=12, fontweight='bold')
        axes[1, 1].grid(axis='x', alpha=0.3)

        plt.tight_layout()
        plt.savefig('virtual_metrology_results.png', dpi=300, bbox_inches='tight')
        plt.show()

# 実行例
print("=" * 60)
print("Virtual Metrologyシステム(エッチングプロセス)")
print("=" * 60)

# VMシステムの初期化
vm = VirtualMetrologySystem()

# プロセスデータの生成
df_process = vm.generate_process_data(n_samples=500)

print(f"\n生成データ数: {len(df_process)}")
print(f"CD範囲: {df_process['cd_measured'].min():.2f} - {df_process['cd_measured'].max():.2f} nm")

# 特徴量の定義
feature_cols = ['rf_power', 'pressure', 'gas_flow_ar', 'gas_flow_cf4',
                'temp_chamber', 'etch_time']

# VMモデルの訓練
results = vm.train_vm_model(df_process, feature_cols)

print(f"\nVMモデル性能:")
print(f"訓練データ R² = {results['r2_train']:.4f}")
print(f"テストデータ R² = {results['r2_test']:.4f}")
print(f"RMSE = {results['rmse_test']:.2f} nm")
print(f"MAE = {results['mae_test']:.2f} nm")

# 新規ウェハの予測例
print(f"\n新規ウェハのCD予測:")
new_wafer = np.array([[1020, 52, 205, 48, 61, 118]])  # 新しいプロセス条件
predicted_cd = vm.predict_cd(new_wafer)
print(f"予測CD: {predicted_cd[0]:.2f} nm")

# 可視化
vm.plot_vm_results(results)

実装のポイント:

📚 まとめ

本章では、ウェハプロセスの統計的管理について学びました。

主要なポイント

🎯 次章予告
第2章では、AIによる欠陥検査とAOI(Automated Optical Inspection)について学びます。 深層学習(CNN)による欠陥分類、セマンティックセグメンテーション、 異物検出など、画像ベースの品質管理技術を習得します。