blob: f00cd7d761f023872f9d368264ca1c995aec8b0f [file] [log] [blame] [edit]
import statistics
from copy import deepcopy
from collections import defaultdict, namedtuple
from data_structs import VprSwitch, MuxEdgeTiming, DriverTiming, SinkTiming
from utils import yield_muxes, add_named_item
# =============================================================================
def linear_regression(xs, ys):
"""
Computes linear regression coefficients
https://en.wikipedia.org/wiki/Simple_linear_regression
Returns a and b coefficients of the function f(y) = a * x + b
"""
x_mean = statistics.mean(xs)
y_mean = statistics.mean(ys)
num, den = 0.0, 0.0
for x, y in zip(xs, ys):
num += (x - x_mean) * (y - y_mean)
den += (x - x_mean) * (x - x_mean)
a = num / den
b = y_mean - a * x_mean
return a, b
# =============================================================================
def create_vpr_switch(type, tdel, r, c):
"""
Creates a VPR switch with the given parameters. Autmatically generates
its name with these parameters encoded.
The VPR switch parameters are:
- type: Switch type. See VPR docs for the available types
- tdel: Constant propagation delay [s]
- r: Internal resistance [ohm]
- c: Internal capacitance (active only when the switch is "on") [F]
"""
# Format the switch name
name = ["sw"]
name += ["T{:>08.6f}".format(tdel * 1e9)]
name += ["R{:>08.6f}".format(r)]
name += ["C{:>010.6f}".format(c * 1e12)]
# Create the VPR switch
switch = VprSwitch(
name="_".join(name),
type=type,
t_del=tdel,
r=r,
c_in=0.0,
c_out=0.0,
c_int=c,
)
return switch
def compute_switchbox_timing_model(switchbox, timing_data):
"""
Processes switchbox timing data.
The timing data is provided in a form of delays for each mux edge (path
from its input pin to the output pin). The delay varies with number of
active loads of the source.
This data is used to compute driver resistances and load capacitances
as well as constant propagation delays.
The timing model assumes that each output of a mux has a certain resistance
and constant propagation time. Then, every load has a capacitance which is
connected when it is active. All capacitances are identical. The input
timing data does not allow to distinguish between them. Additionally, each
load can have a constant propagation delay.
For multiplexers that are driver by switchbox inputs, fake drivers are
assumed solely for the purpose of the timing model.
"""
# A helper struct
Timing = namedtuple("Timing", "driver_r driver_tdel sink_c sink_tdel")
# Delay scaling factor
FACTOR = 1.0
# Error threshold (for reporting) in ns
ERROR_THRESHOLD = 0.4 * 1e-9
# Build a map of sinks for each driver
# For internal drivers key = (stage_id, switch_id, mux_id)
# For external drivers key = (stage_id, input_name)
sink_map = defaultdict(lambda: [])
for connection in switchbox.connections:
src = connection.src
dst = connection.dst
dst_key = (dst.stage_id, dst.switch_id, dst.mux_id, dst.pin_id)
src_key = (src.stage_id, src.switch_id, src.mux_id)
sink_map[src_key].append(dst_key)
for pin in switchbox.inputs.values():
for loc in pin.locs:
dst_key = (loc.stage_id, loc.switch_id, loc.mux_id, loc.pin_id)
src_key = (loc.stage_id, pin.name)
sink_map[src_key].append(dst_key)
# Compute timing model for each driver
driver_timing = {}
for driver, sinks in sink_map.items():
# Collect timing data for each sink edge
edge_timings = {}
for stage_id, switch_id, mux_id, pin_id in sinks:
# Try getting timing data. If not found then probably we are
# computing timing for VCC or GND input.
try:
data = timing_data[stage_id][switch_id][mux_id][pin_id]
except KeyError:
continue
# Sanity check. The number of load counts must be equal to the
# number of sinks for the driver.
assert len(data) == len(sinks)
# Take the worst case (max), convert ns to seconds.
data = {n: max(d) * 1e-9 for n, d in data.items()}
# Store
key = (stage_id, switch_id, mux_id, pin_id)
edge_timings[key] = data
# No timing data, probably it is a VCC or GND input
if not len(edge_timings):
continue
# Compute linear regression for each sink data
coeffs = {}
for sink in sinks:
xs = sorted(edge_timings[sink].keys())
ys = [edge_timings[sink][x] for x in xs]
a, b = linear_regression(xs, ys)
# Cannot have a < 0 (decreasing relation). If such thing happens
# force the regression line to be flat.
if a < 0.0:
print(
"WARNING: For '{} {}' the delay model slope is negative! (a={:.2e})"
.format(switchbox.type, sink, a)
)
a = 0.0
# Cannot have any delay higher than the model. Check if all delays
# lie below the regression line and if not then shift the line up
# accordingly.
for x, y in zip(xs, ys):
t = a * x + b
if y > t:
b += y - t
coeffs[sink] = (a, b)
# Assumed driver resistance [ohm]
driver_r = 1.0
# Compute driver's Tdel
driver_tdel = min([cfs[1] for cfs in coeffs.values()])
# Compute per-sink Tdel
sink_tdel = {s: cfs[1] - driver_tdel for s, cfs in coeffs.items()}
# Compute sink capacitance. Since we have multiple edge timings that
# should yield the same capacitance, compute one for each timing and
# then choose the worst case (max).
sink_cs = {
s: (cfs[0] / (FACTOR * driver_r) - sink_tdel[s])
for s, cfs in coeffs.items()
}
sink_c = max(sink_cs.values())
# Sanity check
assert sink_c >= 0.0, (switchbox.type, sink, sink_c)
# Compute error of the delay model
for sink in sinks:
# Compute for this sink
error = {}
for n, true_delay in edge_timings[sink].items():
model_delay = driver_tdel + FACTOR * driver_r * sink_c * n + sink_tdel[
sink]
error[n] = true_delay - model_delay
max_error = max([abs(e) for e in error.values()])
# Report the error
if max_error > ERROR_THRESHOLD:
print(
"WARNING: Error of the timing model of '{} {}' is too high:"
.format(switchbox.type, sink)
)
print("--------------------------------------------")
print("| # loads | actual | model | error |")
print("|---------+----------+----------+----------|")
for n in edge_timings[sink].keys():
print(
"| {:<8}| {:<9.3f}| {:<9.3f}| {:<9.3f}|".format(
n, 1e9 * edge_timings[sink][n],
1e9 * (edge_timings[sink][n] - error[n]),
1e9 * error[n]
)
)
print("--------------------------------------------")
print("")
# Store the data
driver_timing[driver] = Timing(
driver_r=driver_r,
driver_tdel=driver_tdel,
sink_tdel={s: d
for s, d in sink_tdel.items()},
sink_c=sink_c
)
return driver_timing, sink_map
def populate_switchbox_timing(
switchbox, driver_timing, sink_map, vpr_switches
):
"""
Populates the switchbox timing model by annotating its muxes with the timing
data. Creates new VPR switches with required parameters or uses existing
ones if already created.
"""
# Populate timing data to the switchbox
for driver, timing in driver_timing.items():
# Driver VPR switch
driver_vpr_switch = create_vpr_switch(
type="mux",
tdel=timing.driver_tdel,
r=timing.driver_r,
c=0.0,
)
driver_vpr_switch = add_named_item(
vpr_switches, driver_vpr_switch, driver_vpr_switch.name
)
# Annotate all driver's edges
for sink in sink_map[driver]:
stage_id, switch_id, mux_id, pin_id = sink
# Sink VPR switch
sink_vpr_switch = create_vpr_switch(
type="mux",
tdel=timing.sink_tdel[sink],
r=0.0,
c=timing.sink_c,
)
sink_vpr_switch = add_named_item(
vpr_switches, sink_vpr_switch, sink_vpr_switch.name
)
# Get the mux
stage = switchbox.stages[stage_id]
switch = stage.switches[switch_id]
mux = switch.muxes[mux_id]
assert pin_id not in mux.timing
mux.timing[pin_id] = MuxEdgeTiming(
driver=DriverTiming(
tdel=timing.driver_tdel,
r=timing.driver_r,
vpr_switch=driver_vpr_switch.name
),
sink=SinkTiming(
tdel=timing.sink_tdel,
c=timing.sink_c,
vpr_switch=sink_vpr_switch.name
)
)
def copy_switchbox_timing(src_switchbox, dst_switchbox):
"""
Copies all timing information from the source switchbox to the destination
one.
"""
# Mux timing
for dst_stage, dst_switch, dst_mux in yield_muxes(dst_switchbox):
src_stage = src_switchbox.stages[dst_stage.id]
src_switch = src_stage.switches[dst_switch.id]
src_mux = src_switch.muxes[dst_mux.id]
dst_mux.timing = deepcopy(src_mux.timing)
# =============================================================================
def add_vpr_switches_for_cell(cell_type, cell_timings):
"""
Creates VPR switches for IOPATH delays read from SDF file(s) for the given
cell type.
"""
# Filter timings for the cell
timings = {
k: v
for k, v in cell_timings.items()
if k.startswith(cell_type)
}
# Add VPR switches
vpr_switches = {}
for celltype, cell_data in timings.items():
for instance, inst_data in cell_data.items():
# Add IOPATHs
for timing, timing_data in inst_data.items():
if timing_data["type"].lower() != "iopath":
continue
# Get data
name = "{}.{}.{}.{}".format(
cell_type, instance, timing_data["from_pin"],
timing_data["to_pin"]
)
tdel = timing_data["delay_paths"]["slow"]["avg"]
# Add the switch
sw = VprSwitch(
name=name,
type="mux",
t_del=tdel,
r=0.0,
c_in=0.0,
c_out=0.0,
c_int=0.0,
)
vpr_switches[sw.name] = sw
return vpr_switches