⚡ Minimizing Circuit Power Consumption While Maintaining Performance

Power efficiency is one of the central challenges in modern VLSI and embedded systems design. This post walks through a concrete optimization problem: given a digital circuit with several logic gates, how do we reduce total power dissipation while ensuring the circuit still meets its timing (performance) constraint?


🔧 Problem Setup

Consider a combinational logic circuit with 5 logic gates. Each gate can be configured at one of three voltage/transistor sizing levels (Level 0, 1, 2), trading off power against speed.

The goal is to choose a level for each gate such that:

  • Total power is minimized
  • Critical path delay ≤ deadline (performance constraint is satisfied)

📐 The Math

Each gate $g_i$ has a configurable level $x_i \in {0, 1, 2}$.

Power model (dynamic power):

$$P_i(x_i) = P_{\text{base},i} \cdot \alpha^{x_i}$$

where $\alpha > 1$ means higher levels consume more power.

Delay model (higher level = faster gate):

$$D_i(x_i) = D_{\text{base},i} \cdot \beta^{-x_i}$$

where $\beta > 1$ means higher levels are faster.

Objective:

$$\min \sum_{i=1}^{N} P_i(x_i)$$

Subject to:

$$\sum_{i \in \text{critical path}} D_i(x_i) \leq T_{\text{deadline}}$$

$$x_i \in {0, 1, 2}, \quad \forall i$$


🐍 Python Source Code (Single File)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
# ============================================================
# Circuit Power Minimization under Timing Constraint
# Solved via Integer Linear Programming (scipy) + Brute Force
# Visualized in 2D and 3D
# ============================================================

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from itertools import product as iterproduct
import warnings
warnings.filterwarnings("ignore")

# ─────────────────────────────────────────────────────────────
# 1. CIRCUIT PARAMETERS
# ─────────────────────────────────────────────────────────────
np.random.seed(42)

N_GATES = 5
LEVELS = [0, 1, 2] # configurable level per gate
ALPHA = 1.8 # power scaling factor
BETA = 1.5 # delay scaling factor (inverse)
T_DEADLINE = 12.0 # timing deadline (ns)

# Critical path: gates 0, 2, 4 are on the critical path
CRITICAL_PATH = [0, 2, 4]

# Base power (mW) and base delay (ns) per gate
P_BASE = np.array([3.0, 2.0, 4.0, 1.5, 3.5])
D_BASE = np.array([4.0, 3.5, 5.0, 2.0, 4.5])

def power(gate_idx, level):
"""Power consumption of gate i at given level."""
return P_BASE[gate_idx] * (ALPHA ** level)

def delay(gate_idx, level):
"""Propagation delay of gate i at given level."""
return D_BASE[gate_idx] * (BETA ** (-level))

def total_power(config):
"""Total power for a configuration (tuple of levels)."""
return sum(power(i, config[i]) for i in range(N_GATES))

def critical_delay(config):
"""Sum of delays along the critical path."""
return sum(delay(i, config[i]) for i in CRITICAL_PATH)

def is_feasible(config):
"""Returns True if timing constraint is satisfied."""
return critical_delay(config) <= T_DEADLINE

# ─────────────────────────────────────────────────────────────
# 2. BRUTE-FORCE EXHAUSTIVE SEARCH (3^5 = 243 combinations)
# ─────────────────────────────────────────────────────────────
all_configs = list(iterproduct(LEVELS, repeat=N_GATES))
all_power = np.array([total_power(c) for c in all_configs])
all_delay = np.array([critical_delay(c) for c in all_configs])
feasible_mask = all_delay <= T_DEADLINE

feasible_configs = [c for c, f in zip(all_configs, feasible_mask) if f]
feasible_power = all_power[feasible_mask]
feasible_delay = all_delay[feasible_mask]

best_idx = np.argmin(feasible_power)
best_config = feasible_configs[best_idx]
best_power = feasible_power[best_idx]
best_delay = feasible_delay[best_idx]

print("=" * 55)
print(" CIRCUIT POWER MINIMIZATION RESULT")
print("=" * 55)
print(f" Gate levels (0=slow/low-power, 2=fast/high-power):")
for i in range(N_GATES):
tag = " ← critical" if i in CRITICAL_PATH else ""
print(f" Gate {i}: Level {best_config[i]}{tag}")
print(f"\n Total power : {best_power:.3f} mW")
print(f" Critical delay: {best_delay:.3f} ns (deadline={T_DEADLINE} ns)")
print(f" Feasible solutions found: {feasible_mask.sum()} / {len(all_configs)}")
print("=" * 55)

# ─────────────────────────────────────────────────────────────
# 3. PARETO FRONT — Power vs. Delay
# ─────────────────────────────────────────────────────────────
def pareto_front(powers, delays):
"""Extract Pareto-optimal solutions (min power, min delay)."""
pts = sorted(zip(delays, powers))
pareto = []
min_p = float('inf')
for d, p in pts:
if p < min_p:
pareto.append((d, p))
min_p = p
return zip(*pareto) if pareto else ([], [])

par_d, par_p = pareto_front(all_power, all_delay)

# ─────────────────────────────────────────────────────────────
# 4. VISUALIZATION
# ─────────────────────────────────────────────────────────────
fig = plt.figure(figsize=(18, 13))
fig.patch.set_facecolor('#0f0f1a')

# ── Panel 1: Power vs. Critical Delay scatter ────────────────
ax1 = fig.add_subplot(2, 3, 1)
ax1.set_facecolor('#12122a')

infeas = ~feasible_mask
ax1.scatter(all_delay[infeas], all_power[infeas],
c='#ff4466', alpha=0.3, s=18, label='Infeasible', zorder=2)
ax1.scatter(feasible_delay, feasible_power,
c='#44ddaa', alpha=0.6, s=25, label='Feasible', zorder=3)
ax1.scatter([best_delay], [best_power],
c='#ffdd00', s=120, marker='*', label='Optimal', zorder=5)
ax1.plot(list(par_d), list(par_p),
'w--', lw=1.2, alpha=0.7, label='Pareto front', zorder=4)
ax1.axvline(T_DEADLINE, color='#ff8844', lw=1.5, ls='--', label='Deadline')
ax1.set_xlabel('Critical Path Delay (ns)', color='white')
ax1.set_ylabel('Total Power (mW)', color='white')
ax1.set_title('Power vs. Delay — All Configurations', color='white', fontsize=10)
ax1.legend(fontsize=7, facecolor='#1a1a2e', labelcolor='white')
ax1.tick_params(colors='white')
for sp in ax1.spines.values(): sp.set_edgecolor('#334')

# ── Panel 2: Bar chart — per-gate power at optimal ───────────
ax2 = fig.add_subplot(2, 3, 2)
ax2.set_facecolor('#12122a')

gate_powers_opt = [power(i, best_config[i]) for i in range(N_GATES)]
gate_powers_max = [power(i, 2) for i in range(N_GATES)]
gate_powers_min = [power(i, 0) for i in range(N_GATES)]
colors_bar = ['#ffaa00' if i in CRITICAL_PATH else '#44aaff' for i in range(N_GATES)]

x = np.arange(N_GATES)
ax2.bar(x - 0.25, gate_powers_min, 0.22, color='#334466', alpha=0.8, label='Min (L0)')
ax2.bar(x, gate_powers_opt, 0.22, color=colors_bar, alpha=0.9, label='Optimal')
ax2.bar(x + 0.25, gate_powers_max, 0.22, color='#663333', alpha=0.8, label='Max (L2)')
ax2.set_xticks(x)
ax2.set_xticklabels([f'G{i}\n(L{best_config[i]})' for i in range(N_GATES)], color='white')
ax2.set_ylabel('Power (mW)', color='white')
ax2.set_title('Per-Gate Power: Min / Optimal / Max', color='white', fontsize=10)
ax2.legend(fontsize=7, facecolor='#1a1a2e', labelcolor='white')
ax2.tick_params(colors='white')
for sp in ax2.spines.values(): sp.set_edgecolor('#334')

# ── Panel 3: Bar chart — per-gate delay at optimal ───────────
ax3 = fig.add_subplot(2, 3, 3)
ax3.set_facecolor('#12122a')

gate_delays_opt = [delay(i, best_config[i]) for i in range(N_GATES)]

ax3.barh(range(N_GATES), gate_delays_opt, color=colors_bar, alpha=0.9)
ax3.set_yticks(range(N_GATES))
ax3.set_yticklabels([f'Gate {i}{"★" if i in CRITICAL_PATH else ""}' for i in range(N_GATES)],
color='white')
ax3.set_xlabel('Delay (ns)', color='white')
ax3.set_title('Per-Gate Delay at Optimal Config\n(★ = critical path)', color='white', fontsize=10)
ax3.tick_params(colors='white')
for sp in ax3.spines.values(): sp.set_edgecolor('#334')
# Mark deadline contribution (sum on critical path)
crit_del_sum = sum(gate_delays_opt[i] for i in CRITICAL_PATH)
ax3.text(0.98, 0.05,
f'Critical sum: {crit_del_sum:.2f} ns\nDeadline: {T_DEADLINE} ns',
transform=ax3.transAxes, ha='right', va='bottom',
color='#ffdd00', fontsize=8,
bbox=dict(boxstyle='round,pad=0.3', fc='#1a1a2e', ec='#ffdd00', alpha=0.7))

# ── Panel 4: Heatmap — power landscape (Gate 0 vs Gate 2) ───
ax4 = fig.add_subplot(2, 3, 4)
ax4.set_facecolor('#12122a')

hmap = np.zeros((3, 3))
for l0 in LEVELS:
for l2 in LEVELS:
# Fix gates 1,3,4 at best_config, vary g0 and g2
cfg = list(best_config)
cfg[0] = l0; cfg[2] = l2
hmap[l0, l2] = total_power(tuple(cfg))

im = ax4.imshow(hmap, cmap='RdYlGn_r', origin='lower', aspect='auto')
ax4.set_xticks([0, 1, 2]); ax4.set_yticks([0, 1, 2])
ax4.set_xticklabels(['L0', 'L1', 'L2'], color='white')
ax4.set_yticklabels(['L0', 'L1', 'L2'], color='white')
ax4.set_xlabel('Gate 2 Level', color='white')
ax4.set_ylabel('Gate 0 Level', color='white')
ax4.set_title('Power Heatmap\n(Gate 0 vs Gate 2, others fixed)', color='white', fontsize=10)
for l0 in LEVELS:
for l2 in LEVELS:
ax4.text(l2, l0, f'{hmap[l0,l2]:.1f}', ha='center', va='center',
color='white', fontsize=9, fontweight='bold')
plt.colorbar(im, ax=ax4, label='Total Power (mW)', fraction=0.046, pad=0.04)
ax4.tick_params(colors='white')
# Highlight optimal cell
ax4.add_patch(plt.Rectangle(
(best_config[2] - 0.5, best_config[0] - 0.5), 1, 1,
lw=2.5, edgecolor='#ffdd00', facecolor='none'))

# ── Panel 5: 3D surface — Power vs (Gate0 level, Gate2 level)
ax5 = fig.add_subplot(2, 3, 5, projection='3d')
ax5.set_facecolor('#12122a')

X3, Y3 = np.meshgrid(LEVELS, LEVELS)
Z3 = np.zeros_like(X3, dtype=float)
for i in range(3):
for j in range(3):
cfg = list(best_config); cfg[0] = Y3[i, j]; cfg[2] = X3[i, j]
Z3[i, j] = total_power(tuple(cfg))

surf = ax5.plot_surface(X3, Y3, Z3, cmap='plasma', alpha=0.85, edgecolor='none')
ax5.scatter([best_config[2]], [best_config[0]], [best_power],
color='yellow', s=100, zorder=10, label='Optimal')
ax5.set_xlabel('Gate 2 Level', color='white', labelpad=6)
ax5.set_ylabel('Gate 0 Level', color='white', labelpad=6)
ax5.set_zlabel('Total Power (mW)', color='white', labelpad=6)
ax5.set_title('3D Power Surface\n(Gate 0 & Gate 2 axes)', color='white', fontsize=10)
ax5.tick_params(colors='white')
ax5.xaxis.pane.fill = False; ax5.yaxis.pane.fill = False; ax5.zaxis.pane.fill = False
ax5.xaxis.pane.set_edgecolor('#334'); ax5.yaxis.pane.set_edgecolor('#334')
ax5.zaxis.pane.set_edgecolor('#334')
ax5.legend(fontsize=8, facecolor='#1a1a2e', labelcolor='white')

# ── Panel 6: Sensitivity analysis — sweep each gate ─────────
ax6 = fig.add_subplot(2, 3, 6)
ax6.set_facecolor('#12122a')

gate_colors = ['#ff6655', '#55aaff', '#ffaa00', '#88ff66', '#ee55ff']
for gi in range(N_GATES):
sweep_power = []
for lv in LEVELS:
cfg = list(best_config); cfg[gi] = lv
sweep_power.append(total_power(tuple(cfg)))
ax6.plot(LEVELS, sweep_power,
marker='o', lw=2, ms=7,
color=gate_colors[gi],
label=f'Gate {gi}{"★" if gi in CRITICAL_PATH else ""}')

ax6.axhline(best_power, color='#ffdd00', lw=1.2, ls='--', label=f'Optimal ({best_power:.1f} mW)')
ax6.set_xticks(LEVELS)
ax6.set_xticklabels(['Level 0\n(low-pwr)', 'Level 1\n(mid)', 'Level 2\n(high-pwr)'], color='white')
ax6.set_ylabel('Total Power (mW)', color='white')
ax6.set_title('Sensitivity: Total Power vs Gate Level', color='white', fontsize=10)
ax6.legend(fontsize=7, facecolor='#1a1a2e', labelcolor='white')
ax6.tick_params(colors='white')
for sp in ax6.spines.values(): sp.set_edgecolor('#334')

# ── Final layout ─────────────────────────────────────────────
plt.suptitle('Circuit Power Minimization — Full Analysis', color='white',
fontsize=14, fontweight='bold', y=1.01)
plt.tight_layout()
plt.savefig('circuit_power_opt.png', dpi=150, bbox_inches='tight',
facecolor='#0f0f1a')
plt.show()
print("Plot saved as circuit_power_opt.png")

🔍 Code Walkthrough

Section 1 — Parameters & Models

We define 5 gates, each with a base power $P_{\text{base},i}$ and base delay $D_{\text{base},i}$. Two simple closed-form formulas govern how those values scale:

1
2
3
4
5
def power(gate_idx, level):
return P_BASE[gate_idx] * (ALPHA ** level)

def delay(gate_idx, level):
return D_BASE[gate_idx] * (BETA ** (-level))
  • ALPHA = 1.8 means jumping from Level 0 → Level 2 multiplies power by $1.8^2 = 3.24\times$.
  • BETA = 1.5 means Level 2 cuts delay to $1/1.5^2 \approx 0.44\times$ of Level 0.

This tension is exactly the core trade-off in voltage/transistor scaling: faster gates burn more power.


With $N=5$ gates and 3 levels each, the search space has exactly $3^5 = 243$ configurations — small enough to evaluate all of them in microseconds:

1
all_configs = list(iterproduct(LEVELS, repeat=N_GATES))

For each configuration we check feasibility:

$$\sum_{i \in {0,2,4}} D_i(x_i) \leq T_{\text{deadline}} = 12 \text{ ns}$$

Then among all feasible ones, we select the minimum-power configuration.


Section 3 — Pareto Front

The Pareto front is the set of configurations where you cannot reduce power further without violating the delay constraint (and vice versa). The algorithm sorts by delay, then sweeps for monotonically decreasing power:

1
2
3
4
5
6
7
8
9
def pareto_front(powers, delays):
pts = sorted(zip(delays, powers))
pareto = []
min_p = float('inf')
for d, p in pts:
if p < min_p:
pareto.append((d, p))
min_p = p
return zip(*pareto) if pareto else ([], [])

This is the engineering design frontier — every point on it represents a valid Pareto-optimal design.


📊 Graph-by-Graph Explanation

Panel 1 — Power vs. Delay Scatter

Every dot is one of the 243 configurations. Red dots are infeasible (too slow). Green dots meet the deadline. The yellow star is the optimal solution. The dashed white line is the Pareto front, and the orange vertical line is the 12 ns deadline wall.

Panel 2 — Per-Gate Power Bar Chart

For each gate, we compare its power at Level 0 (minimum), the optimal level chosen by the solver, and Level 2 (maximum). Yellow bars are critical-path gates; blue bars are off-critical-path gates. Notice how off-critical gates tend to stay at Level 0 (no need to speed them up).

Panel 3 — Per-Gate Delay

A horizontal bar chart showing the delay contributed by each gate at the optimal configuration. Gates marked ★ are on the critical path. The inset text shows the critical path sum vs. the deadline — you can verify the constraint is tight but satisfied.

Panel 4 — Power Heatmap (Gate 0 × Gate 2)

A $3\times 3$ grid where the axes are the levels of Gate 0 (y) and Gate 2 (x), with all other gates fixed at their optimal values. Color encodes total circuit power (red = high, green = low). The yellow box highlights the optimal cell. This is a classic design space exploration view used by EDA tools.

Panel 5 — 3D Power Surface (the highlight!)

The same data as the heatmap, lifted into 3D. The plasma colormap surface lets you immediately see the convex shape of the power landscape. The yellow dot marks the global optimum. You can visualize this as the “bowl” the optimizer is searching — the optimal point sits at the minimum of this surface subject to the timing floor.

Panel 6 — Sensitivity Analysis

Each line shows what happens to total circuit power when you sweep one gate across Levels 0 → 1 → 2, keeping all others fixed at the optimum. Steeper lines indicate gates with high power sensitivity — adjusting those gates has the most impact on the objective. This tells a designer which gates are worth careful optimization.


📋 Execution Results

=======================================================
  CIRCUIT POWER MINIMIZATION RESULT
=======================================================
  Gate levels (0=slow/low-power, 2=fast/high-power):
    Gate 0: Level 0 ← critical
    Gate 1: Level 0
    Gate 2: Level 0 ← critical
    Gate 3: Level 0
    Gate 4: Level 1 ← critical

  Total power   : 16.800 mW
  Critical delay: 12.000 ns  (deadline=12.0 ns)
  Feasible solutions found: 225 / 243
=======================================================

Plot saved as circuit_power_opt.png

💡 Key Takeaways

The optimization reveals a fundamental insight in low-power design: not every gate needs to run at maximum speed. Gates that are off the critical path can safely be held at Level 0 (low power, slow), because their delay does not limit circuit performance. Only the critical-path gates (0, 2, 4 in our example) need to be tuned upward — and even then, only to the minimum level that satisfies the timing deadline. This selective assignment is the essence of slack-based power optimization in real VLSI CAD flows.