Divide and Conquer (DnC) Hardware Cost Modeling

This notebook provides a structured workflow for calculating ASIC cell gate count estimations for specific layer designs using actual physical synthesis points. It maps data plane attributes against an Arithmetic Cost Estimation (ACE) mathematical regression topology.

Analytical Target Function:

$$\text{MAC Area} = a \cdot (X_{\text{bits}} \cdot W_{\text{bits}}) + b \cdot Z_{\text{bits}} + c$$ Where:

  • $X_{\text{bits}}$: Input Bit-width

  • $W_{\text{bits}}$: Weight Parameter Bit-width

  • $Z_{\text{bits}}$: Accumulator register Bit-width

  • $a, b, c$: Regression parameters reflecting actual hardware implementation metrics.

Prerequisites

Ensure your Python environment contains the necessary data management and calculation toolsets:

# !pip install numpy pandas scipy matplotlib keras
import io
import numpy as np
import keras.ops.numpy as knp
import pandas as pd
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit

1. Synthesis Sizing Data and Hardware Mappings

# Rule-of-thumb mapping between bits and gates in memory area estimate.
MemoryGatesPerBit = {
    "Register": 10.0,
    "SRAM": 1.0,
    "ROM": 0.1,
}

# Previously calculated 3D polynomial coefficients with relative MAE < 5%.
MAC_POLY3D_PARAMS = knp.array([7.70469119, 13.76199652, -92.15756665])

# Raw hardware target area measurements generated from synthesis runs
MAC24 = pd.read_csv(io.StringIO("\n283,280,286,313,325,336,356,,\n274,290,325,372,401,428,485,,\n285,325,388,510,568,614,713,,\n308,372,509,750,865,1002,1167,,\n336,427,617,1003,1151,1309,,,\n356,480,722,1165,,,,\n"), header=None)

MAC32 = pd.read_csv(io.StringIO("\n391,365,377,410,453,433,458,507,\n364,382,418,466,497,521,578,685,\n378,418,485,594,659,721,832,1035,\n408,466,596,843,1029,1151,1321,1642,\n432,521,724,1153,1363,1512,1797,,\n457,578,830,1330,1551,1782,2273,,\n"), header=None)

MAC40 = pd.read_csv(io.StringIO("\n458,457,470,500,522,527,551,605,664\n457,475,513,561,597,616,670,782,888\n470,513,579,699,766,816,928,1150,1358\n499,561,699,996,1161,1273,1499,1850,2189\n527,612,818,1275,1545,1691,2054,2516,\n549,670,927,1496,1798,2035,2490,3294,\n"), header=None)

MAC48 = pd.read_csv(io.StringIO("\n595,550,566,594,659,624,642,694,745\n551,566,607,654,727,707,763,881,984\n566,607,679,794,871,921,1017,1270,1489\n594,655,793,1097,1285,1401,1668,2101,2378\n624,711,921,1397,1816,1950,2277,2763,3301\n642,762,1015,1669,1974,2264,2718,3631,4415\n"), header=None)

2. Defining Cost Regression and Data Alignment Pipelines

def mac_gates_polynomial_3d(xyz, a, b, c):
    """Models hardware MAC area as a sum of multiplier, accumulator, and structural offsets."""
    x, y, z = xyz
    return a * x * y + b * z + c

def gen_mac_gate_model(do_plot=False):
    """Fits the polynomial coefficients based on raw data tables."""
    # Build dimension indexes matching raw matrices shapes
    abit = np.repeat(knp.array([24, 32, 40, 48]), 54)
    wbit = np.tile(np.repeat(knp.array([1, 2, 4, 8, 12, 16]), 9), 4)
    xbit = np.tile(knp.array([1, 2, 4, 8, 10, 12, 16, 24, 32]), 24)

    mac_arrs = []
    mac_arrs_index = {}
    valid_index = []
    start_pos = 0

    for mac_acc, acc_bits in zip([MAC24, MAC32, MAC40, MAC48], [24, 32, 40, 48]):
        cur_mac = mac_acc.to_numpy().reshape(-1)
        cur_valid_index = ~np.isnan(cur_mac)
        cur_valid_mac = cur_mac[cur_valid_index]
        
        end_pos = start_pos + len(cur_valid_mac)
        mac_arrs_index[acc_bits] = (start_pos, end_pos)
        mac_arrs += list(cur_valid_mac)
        start_pos = end_pos
        valid_index += list(cur_valid_index)

    # Strip invalid/NaN missing parameters mapping records
    xbit = xbit[valid_index]
    wbit = wbit[valid_index]
    abit = abit[valid_index]

    # Execute multidimensional optimization curve fitting
    params, covariance = curve_fit(mac_gates_polynomial_3d, (xbit, wbit, abit), mac_arrs)
    parameter_std_deviation = knp.sqrt(np.diag(covariance))

    mac_predict = mac_gates_polynomial_3d((xbit, wbit, abit), *params)
    mae_predict = knp.mean(knp.abs(mac_predict - mac_arrs)) / knp.mean(mac_arrs)

    if do_plot:
        # Render 3D tracking scattered plots
        fig = plt.figure(figsize=(10, 6))
        ax = fig.add_subplot(111, projection='3d')
        ax.scatter(xbit, wbit, mac_arrs, c=abit, cmap='magma', label='Synthesis Data')
        ax.set_xlabel('Input Bit-width (X)')
        ax.set_ylabel('Weight Bit-width (W)')
        ax.set_zlabel('Gate Count (Area)')
        plt.title('Raw Synthesis Sizing Vectors')
        plt.show()

        # Generate surface mesh plots for distinct configurations
        x_fit = np.linspace(min(xbit), max(xbit), 30)
        w_fit = np.linspace(min(wbit), max(wbit), 30)
        xmesh, wmesh = np.meshgrid(x_fit, w_fit)

        fig = plt.figure(figsize=(14, 10))
        for idx, acc_bits in enumerate([24, 32, 40, 48], 1):
            ax = fig.add_subplot(2, 2, idx, projection='3d')
            sp, ep = mac_arrs_index[acc_bits]
            
            ax.scatter(xbit[sp:ep], wbit[sp:ep], mac_arrs[sp:ep], color='red', label='Data')
            amesh = np.full((30, 30), acc_bits)
            poly_fit = mac_gates_polynomial_3d((xmesh, wmesh, amesh), *params)
            
            ax.plot_surface(xmesh, wmesh, poly_fit, cmap='viridis', alpha=0.6)
            ax.set_title(f'Accumulator Width: {acc_bits} Bits')
            ax.set_xlabel('X')
            ax.set_ylabel('W')
        plt.tight_layout()
        plt.show()

    return params, mae_predict, parameter_std_deviation

3. Cost Evaluation Functions

def get_ace_mac_gates(xbit, wbit, abit, regen_params=False):
    """Estimates localized macro gate deployment footprints."""
    if regen_params:
        mac_params, _, _ = gen_mac_gate_model(do_plot=True)
    else:
        mac_params = MAC_POLY3D_PARAMS

    return mac_gates_polynomial_3d((xbit, wbit, abit), *mac_params)

4. Run Optimization and Predict Sizing Costs

We now trigger the calibration optimizer to determine parameters $a, b, c$, evaluate fitness errors, and test arbitrary layer structural predictions.

# Run model calibration and generate visualization plots
fitted_params, mae, std_dev = gen_mac_gate_model(do_plot=True)

print("=== OPTIMIZATION RECOVERY LOG ===")
print(f"Fitted Parameter 'a' (Mult Cost): {fitted_params[0]:.4f}")
print(f"Fitted Parameter 'b' (Accum Cost): {fitted_params[1]:.4f}")
print(f"Fitted Parameter 'c' (Base Offset): {fitted_params[2]:.4f}")
print(f"Model Mean Absolute Error (MAE):     {mae*100:.2f}%")

print("\n=== ARBITRARY HARDWARE PREDICTION TEST ===")
# Estimate gate costs for a custom hardware configuration: 4-bit inputs, 4-bit weights, 32-bit accumulator
estimated_gates = get_ace_mac_gates(xbit=4, wbit=4, abit=32, regen_params=False)
print(f"Estimated total logic gates for (X=4, W=4, Acc=32): {estimated_gates:.2f} Gates")
../_images/8f45a4b6d69d435a651e6540bbbe4d0e4abb9291ff5db04eefaf6040c506e0fc.png ../_images/f437f63b3ad0fb81bf6b4441b97c9afc45345ff51d532615f6bd5c1ce533e3d8.png
=== OPTIMIZATION RECOVERY LOG ===
Fitted Parameter 'a' (Mult Cost): 7.7047
Fitted Parameter 'b' (Accum Cost): 13.7620
Fitted Parameter 'c' (Base Offset): -92.1576
Model Mean Absolute Error (MAE):     4.64%

=== ARBITRARY HARDWARE PREDICTION TEST ===
Estimated total logic gates for (X=4, W=4, Acc=32): 471.50 Gates