A larger gate drives its load faster and occupies more area. Sizing up gates on the critical path shortens arrival one stage at a time.
The model below uses EXAMPLE parameters with units and a stated formula. Delay of one gate is its base delay divided by its size, and area is its base area times its size.
EXAMPLE parameters: delays in delay units, areas in teaching-library area units
three gates, base delays 4.0, 3.0, 3.0; base areas 2, 2, 2 (one NAND2 each)
required time = 6.0; rule: delay(size) = base delay / size, area(size) = base area x size
start: arrival = 4.0 + 3.0 + 3.0 = 10.0; slack = 6.0 - 10.0 = -4.0; area = 6
upsize gate 0 (slowest, 4.0): delays 2.0, 3.0, 3.0; arrival = 8.0; slack = -2.0; area = 8
upsize gate 1 (slowest, 3.0): delays 2.0, 1.5, 3.0; arrival = 6.5; slack = -0.5; area = 10
upsize gate 2 (slowest, 3.0): delays 2.0, 1.5, 1.5; arrival = 5.0; slack = +1.0; area = 12
stop with slack >= 0; growth = 12 - 6 = 6 area units over 3 upsizesThe program below runs that loop: upsize the slowest gate until slack reaches zero. Its checks pin every arrival, size, and area from the hand arithmetic above.
import sys
BASE_DELAY = [4.0, 3.0, 3.0]
BASE_AREA = [2, 2, 2]
REQUIRED = 6.0
MAX_SIZE = 4
def total(xs):
s = 0.0
for x in xs:
s += x
return s
def close(got, want):
return abs(got - want) < 1e-9
sizes = [1, 1, 1]
start_arr = total([BASE_DELAY[i] / sizes[i] for i in range(3)])
after = []
guard = 0
while True:
delays = [BASE_DELAY[i] / sizes[i] for i in range(3)]
arr = total(delays)
slack = REQUIRED - arr
if slack >= 0.0:
break
worst = 0
for i in range(1, 3):
if delays[i] > delays[worst]:
worst = i
if sizes[worst] >= MAX_SIZE:
print("FAIL: size cap reached with slack " + str(slack))
sys.exit(1)
sizes[worst] += 1
guard += 1
if guard > 100:
print("FAIL: loop did not settle")
sys.exit(1)
after.append(total([BASE_DELAY[i] / sizes[i] for i in range(3)]))
end_arr = total([BASE_DELAY[i] / sizes[i] for i in range(3)])
end_slack = REQUIRED - end_arr
end_area = BASE_AREA[0] * sizes[0] + BASE_AREA[1] * sizes[1] + BASE_AREA[2] * sizes[2]
if not close(start_arr, 10.0):
print("FAIL: start arrival " + str(start_arr) + " want 10.0")
sys.exit(1)
if after != [8.0, 6.5, 5.0]:
print("FAIL: trace " + str(after) + " want [8.0, 6.5, 5.0]")
sys.exit(1)
if sizes != [2, 2, 2]:
print("FAIL: sizes " + str(sizes) + " want [2, 2, 2]")
sys.exit(1)
if not close(end_slack, 1.0):
print("FAIL: end slack " + str(end_slack) + " want 1.0")
sys.exit(1)
if end_area != 12:
print("FAIL: end area " + str(end_area) + " want 12")
sys.exit(1)
print("PASS: sizing loop moves slack -4.0 to +1.0 in 3 upsizes, area 6 to 12")Path groups collect paths with a shared target, such as core logic and IO paths. Weights state which group matters more when one transform cannot serve both.
The numbers below are illustrative: relative weights whose scale differs between tools. A heavier group draws effort first, and a lighter group may wait several passes.
illustrative groups (not tool output; weight scales differ between tools)
group core: register-to-register paths, weight 10, draws effort first
group io: input-to-register and register-to-output paths, weight 5
group slow: paths with relaxed targets, weight 1, served after the restSlack is still -0.5 after the second upsize. The loop stops only at non-negative slack, reached at 5.0.
Core paths draw effort first. Slow paths are served after the heavier groups stop improving.