|
|
@ -1,5 +1,7 @@ |
|
|
|
import numpy as np |
|
|
|
|
|
|
|
import math |
|
|
|
import math |
|
|
|
|
|
|
|
from bisect import bisect, insort_left |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import numpy as np |
|
|
|
from . import numba |
|
|
|
from . import numba |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -8,8 +10,74 @@ TMAX_OVL = np.float32(1.1 * 2 ** 127) # almost np.PINF with overflow mark |
|
|
|
TMIN = np.float32(-2 ** 127) # almost np.NINF for 32-bit floating point values |
|
|
|
TMIN = np.float32(-2 ** 127) # almost np.NINF for 32-bit floating point values |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Heap: |
|
|
|
|
|
|
|
def __init__(self): |
|
|
|
|
|
|
|
self.chunks = dict() # map start location to chunk size |
|
|
|
|
|
|
|
self.released = list() # chunks that were released |
|
|
|
|
|
|
|
self.current_size = 0 |
|
|
|
|
|
|
|
self.max_size = 0 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def alloc(self, size): |
|
|
|
|
|
|
|
for idx, loc in enumerate(self.released): |
|
|
|
|
|
|
|
if self.chunks[loc] == size: |
|
|
|
|
|
|
|
del self.released[idx] |
|
|
|
|
|
|
|
return loc |
|
|
|
|
|
|
|
elif self.chunks[loc] > size: # split chunk |
|
|
|
|
|
|
|
chunksize = self.chunks[loc] |
|
|
|
|
|
|
|
self.chunks[loc] = size |
|
|
|
|
|
|
|
self.chunks[loc + size] = chunksize - size |
|
|
|
|
|
|
|
self.released[idx] = loc + size # move released pointer: loc -> loc+size |
|
|
|
|
|
|
|
return loc |
|
|
|
|
|
|
|
# no previously released chunk; make new one |
|
|
|
|
|
|
|
loc = self.current_size |
|
|
|
|
|
|
|
self.chunks[loc] = size |
|
|
|
|
|
|
|
self.current_size += size |
|
|
|
|
|
|
|
self.max_size = max(self.max_size, self.current_size) |
|
|
|
|
|
|
|
return loc |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def free(self, loc): |
|
|
|
|
|
|
|
size = self.chunks[loc] |
|
|
|
|
|
|
|
if loc + size == self.current_size: # end of managed area, remove chunk |
|
|
|
|
|
|
|
del self.chunks[loc] |
|
|
|
|
|
|
|
self.current_size -= size |
|
|
|
|
|
|
|
# check and remove prev chunk if free |
|
|
|
|
|
|
|
if len(self.released) > 0: |
|
|
|
|
|
|
|
prev = self.released[-1] |
|
|
|
|
|
|
|
if prev + self.chunks[prev] == self.current_size: |
|
|
|
|
|
|
|
chunksize = self.chunks[prev] |
|
|
|
|
|
|
|
del self.chunks[prev] |
|
|
|
|
|
|
|
del self.released[-1] |
|
|
|
|
|
|
|
self.current_size -= chunksize |
|
|
|
|
|
|
|
return |
|
|
|
|
|
|
|
released_idx = bisect(self.released, loc) |
|
|
|
|
|
|
|
if released_idx < len(self.released) and loc + size == self.released[released_idx]: # next chunk is free, merge |
|
|
|
|
|
|
|
chunksize = size + self.chunks[loc + size] |
|
|
|
|
|
|
|
del self.chunks[loc + size] |
|
|
|
|
|
|
|
self.chunks[loc] = chunksize |
|
|
|
|
|
|
|
size = self.chunks[loc] |
|
|
|
|
|
|
|
self.released[released_idx] = loc |
|
|
|
|
|
|
|
else: |
|
|
|
|
|
|
|
insort_left(self.released, loc) # put in a new release |
|
|
|
|
|
|
|
if released_idx > 0: # check if previous chunk is free |
|
|
|
|
|
|
|
prev = self.released[released_idx - 1] |
|
|
|
|
|
|
|
if prev + self.chunks[prev] == loc: # previous chunk is adjacent to freed one, merge |
|
|
|
|
|
|
|
chunksize = size + self.chunks[prev] |
|
|
|
|
|
|
|
del self.chunks[loc] |
|
|
|
|
|
|
|
self.chunks[prev] = chunksize |
|
|
|
|
|
|
|
del self.released[released_idx] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def __repr__(self): |
|
|
|
|
|
|
|
r = [] |
|
|
|
|
|
|
|
for loc in sorted(self.chunks.keys()): |
|
|
|
|
|
|
|
size = self.chunks[loc] |
|
|
|
|
|
|
|
released_idx = bisect(self.released, loc) |
|
|
|
|
|
|
|
is_released = released_idx > 0 and len(self.released) > 0 and self.released[released_idx - 1] == loc |
|
|
|
|
|
|
|
r.append(f'{loc:5d}: {"free" if is_released else "used"} {size}') |
|
|
|
|
|
|
|
return "\n".join(r) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class WaveSim: |
|
|
|
class WaveSim: |
|
|
|
def __init__(self, circuit, timing, sims=8, wavecaps=16): |
|
|
|
def __init__(self, circuit, timing, sims=8, wavecaps=16, strip_forks=False, keep_waveforms=True): |
|
|
|
self.circuit = circuit |
|
|
|
self.circuit = circuit |
|
|
|
self.sims = sims |
|
|
|
self.sims = sims |
|
|
|
self.overflows = 0 |
|
|
|
self.overflows = 0 |
|
|
@ -24,65 +92,35 @@ class WaveSim: |
|
|
|
|
|
|
|
|
|
|
|
intf_wavecap = 4 # sufficient for storing only 1 transition. |
|
|
|
intf_wavecap = 4 # sufficient for storing only 1 transition. |
|
|
|
|
|
|
|
|
|
|
|
# state allocation table. maps line and interface indices to self.state memory locations |
|
|
|
# indices for state allocation table (sat) |
|
|
|
|
|
|
|
|
|
|
|
self.sat = np.zeros((len(circuit.lines) + 2 + 2 * len(self.interface), 3), dtype='int') |
|
|
|
|
|
|
|
self.sat[:, 0] = -1 |
|
|
|
|
|
|
|
filled = 0 |
|
|
|
|
|
|
|
for lidx, cap in enumerate(wavecaps): |
|
|
|
|
|
|
|
self.sat[lidx] = filled, cap, 0 |
|
|
|
|
|
|
|
filled += cap |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self.zero_idx = len(circuit.lines) |
|
|
|
self.zero_idx = len(circuit.lines) |
|
|
|
self.sat[self.zero_idx] = filled, intf_wavecap, 0 |
|
|
|
|
|
|
|
filled += intf_wavecap |
|
|
|
|
|
|
|
self.tmp_idx = self.zero_idx + 1 |
|
|
|
self.tmp_idx = self.zero_idx + 1 |
|
|
|
self.sat[self.tmp_idx] = filled, intf_wavecap, 0 |
|
|
|
|
|
|
|
filled += intf_wavecap |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self.ppi_offset = self.tmp_idx + 1 |
|
|
|
self.ppi_offset = self.tmp_idx + 1 |
|
|
|
self.ppo_offset = self.ppi_offset + len(self.interface) |
|
|
|
self.ppo_offset = self.ppi_offset + len(self.interface) |
|
|
|
for i, n in enumerate(self.interface): |
|
|
|
self.sat_length = self.ppo_offset + len(self.interface) |
|
|
|
if len(n.outs) > 0: |
|
|
|
|
|
|
|
self.sat[self.ppi_offset + i] = filled, intf_wavecap, 0 |
|
|
|
|
|
|
|
filled += intf_wavecap |
|
|
|
|
|
|
|
if len(n.ins) > 0: |
|
|
|
|
|
|
|
self.sat[self.ppo_offset + i] = self.sat[n.ins[0].index] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# pad timing |
|
|
|
|
|
|
|
self.timing = np.zeros((len(self.sat), 2, 2)) |
|
|
|
|
|
|
|
self.timing[:len(timing)] = timing |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# allocate self.state |
|
|
|
# translate circuit structure into self.ops |
|
|
|
self.state = np.zeros((filled, sims), dtype='float32') + TMAX |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# generate self.ops |
|
|
|
|
|
|
|
ops = [] |
|
|
|
ops = [] |
|
|
|
interface_dict = dict([(n, i) for i, n in enumerate(self.interface)]) |
|
|
|
interface_dict = dict([(n, i) for i, n in enumerate(self.interface)]) |
|
|
|
for n in circuit.topological_order(): |
|
|
|
for n in circuit.topological_order(): |
|
|
|
if n in interface_dict: |
|
|
|
if n in interface_dict: |
|
|
|
inp_idx = self.ppi_offset + interface_dict[n] |
|
|
|
inp_idx = self.ppi_offset + interface_dict[n] |
|
|
|
if len(n.outs) > 0 and n.outs[0] is not None: |
|
|
|
if len(n.outs) > 0 and n.outs[0] is not None: # first output of a PI/PPI |
|
|
|
ops.append((0b1010, n.outs[0].index, inp_idx, self.zero_idx)) |
|
|
|
ops.append((0b1010, n.outs[0].index, inp_idx, self.zero_idx)) |
|
|
|
if 'dff' in n.kind.lower(): |
|
|
|
if 'dff' in n.kind.lower(): # second output of DFF is inverted |
|
|
|
if len(n.outs) > 1 and n.outs[1] is not None: |
|
|
|
if len(n.outs) > 1 and n.outs[1] is not None: |
|
|
|
ops.append((0b0101, n.outs[1].index, inp_idx, self.zero_idx)) |
|
|
|
ops.append((0b0101, n.outs[1].index, inp_idx, self.zero_idx)) |
|
|
|
else: |
|
|
|
else: # if not DFF, no output is inverted. |
|
|
|
for o_line in n.outs[1:]: |
|
|
|
for o_line in n.outs[1:]: |
|
|
|
if o_line is not None: |
|
|
|
if o_line is not None: |
|
|
|
ops.append((0b1010, o_line.index, inp_idx, self.zero_idx)) |
|
|
|
ops.append((0b1010, o_line.index, inp_idx, self.zero_idx)) |
|
|
|
else: |
|
|
|
else: # regular node, not PI/PPI or PO/PPO |
|
|
|
o0_idx = self.tmp_idx |
|
|
|
o0_idx = n.outs[0].index if len(n.outs) > 0 and n.outs[0] is not None else self.tmp_idx |
|
|
|
i0_idx = self.zero_idx |
|
|
|
i0_idx = n.ins[0].index if len(n.ins) > 0 and n.ins[0] is not None else self.zero_idx |
|
|
|
i1_idx = self.zero_idx |
|
|
|
i1_idx = n.ins[1].index if len(n.ins) > 1 and n.ins[1] is not None else self.zero_idx |
|
|
|
if len(n.outs) > 0 and n.outs[0] is not None: |
|
|
|
|
|
|
|
o0_idx = n.outs[0].index |
|
|
|
|
|
|
|
else: |
|
|
|
|
|
|
|
print(f'no outputs for {n}') |
|
|
|
|
|
|
|
if len(n.ins) > 0 and n.ins[0] is not None: i0_idx = n.ins[0].index |
|
|
|
|
|
|
|
if len(n.ins) > 1 and n.ins[1] is not None: i1_idx = n.ins[1].index |
|
|
|
|
|
|
|
kind = n.kind.lower() |
|
|
|
kind = n.kind.lower() |
|
|
|
if kind == '__fork__': |
|
|
|
if kind == '__fork__': |
|
|
|
|
|
|
|
if not strip_forks: |
|
|
|
for o_line in n.outs: |
|
|
|
for o_line in n.outs: |
|
|
|
ops.append((0b1010, o_line.index, i0_idx, i1_idx)) |
|
|
|
ops.append((0b1010, o_line.index, i0_idx, i1_idx)) |
|
|
|
elif kind.startswith('nand'): |
|
|
|
elif kind.startswith('nand'): |
|
|
@ -109,18 +147,91 @@ class WaveSim: |
|
|
|
print('unknown gate type', kind) |
|
|
|
print('unknown gate type', kind) |
|
|
|
self.ops = np.asarray(ops, dtype='int32') |
|
|
|
self.ops = np.asarray(ops, dtype='int32') |
|
|
|
|
|
|
|
|
|
|
|
# generate level data |
|
|
|
# create a map from fanout lines to stem lines for fork stripping |
|
|
|
levels = np.zeros(len(self.sat), dtype='int32') |
|
|
|
stems = np.zeros(self.sat_length, dtype='int32') - 1 # default to -1: 'no fanout line' |
|
|
|
|
|
|
|
if strip_forks: |
|
|
|
|
|
|
|
for f in circuit.forks.values(): |
|
|
|
|
|
|
|
prev_line = f.ins[0] |
|
|
|
|
|
|
|
while prev_line.driver.kind == '__fork__': |
|
|
|
|
|
|
|
prev_line = prev_line.driver.ins[0] |
|
|
|
|
|
|
|
stem_idx = prev_line.index |
|
|
|
|
|
|
|
for ol in f.outs: |
|
|
|
|
|
|
|
stems[ol.index] = stem_idx |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# calculate level (distance from PI/PPI) and reference count for each line |
|
|
|
|
|
|
|
levels = np.zeros(self.sat_length, dtype='int32') |
|
|
|
|
|
|
|
ref_count = np.zeros(self.sat_length, dtype='int32') |
|
|
|
level_starts = [0] |
|
|
|
level_starts = [0] |
|
|
|
current_level = 1 |
|
|
|
current_level = 1 |
|
|
|
for i, op in enumerate(self.ops): |
|
|
|
for i, op in enumerate(self.ops): |
|
|
|
if levels[op[2]] >= current_level or levels[op[3]] >= current_level: |
|
|
|
# if we fork-strip, always take the stems for determining fan-in level |
|
|
|
|
|
|
|
i0_idx = stems[op[2]] if stems[op[2]] >= 0 else op[2] |
|
|
|
|
|
|
|
i1_idx = stems[op[3]] if stems[op[3]] >= 0 else op[3] |
|
|
|
|
|
|
|
if levels[i0_idx] >= current_level or levels[i1_idx] >= current_level: |
|
|
|
current_level += 1 |
|
|
|
current_level += 1 |
|
|
|
level_starts.append(i) |
|
|
|
level_starts.append(i) |
|
|
|
levels[op[1]] = current_level |
|
|
|
levels[op[1]] = current_level # set level of the output line |
|
|
|
|
|
|
|
ref_count[i0_idx] += 1 |
|
|
|
|
|
|
|
ref_count[i1_idx] += 1 |
|
|
|
self.level_starts = np.asarray(level_starts, dtype='int32') |
|
|
|
self.level_starts = np.asarray(level_starts, dtype='int32') |
|
|
|
self.level_stops = np.asarray(level_starts[1:] + [len(self.ops)], dtype='int32') |
|
|
|
self.level_stops = np.asarray(level_starts[1:] + [len(self.ops)], dtype='int32') |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# state allocation table. maps line and interface indices to self.state memory locations |
|
|
|
|
|
|
|
self.sat = np.zeros((self.sat_length, 3), dtype='int') |
|
|
|
|
|
|
|
self.sat[:, 0] = -1 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
h = Heap() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# allocate and keep memory for special fields |
|
|
|
|
|
|
|
self.sat[self.zero_idx] = h.alloc(intf_wavecap), intf_wavecap, 0 |
|
|
|
|
|
|
|
self.sat[self.tmp_idx] = h.alloc(intf_wavecap), intf_wavecap, 0 |
|
|
|
|
|
|
|
ref_count[self.zero_idx] += 1 |
|
|
|
|
|
|
|
ref_count[self.tmp_idx] += 1 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# allocate and keep memory for PI/PPI, keep memory for PO/PPO (allocated later) |
|
|
|
|
|
|
|
for i, n in enumerate(self.interface): |
|
|
|
|
|
|
|
if len(n.outs) > 0: |
|
|
|
|
|
|
|
self.sat[self.ppi_offset + i] = h.alloc(intf_wavecap), intf_wavecap, 0 |
|
|
|
|
|
|
|
ref_count[self.ppi_offset + i] += 1 |
|
|
|
|
|
|
|
if len(n.ins) > 0: |
|
|
|
|
|
|
|
i0_idx = stems[n.ins[0].index] if stems[n.ins[0].index] >= 0 else n.ins[0].index |
|
|
|
|
|
|
|
ref_count[i0_idx] += 1 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# allocate memory for the rest of the circuit |
|
|
|
|
|
|
|
for op_start, op_stop in zip(self.level_starts, self.level_stops): |
|
|
|
|
|
|
|
free_list = [] |
|
|
|
|
|
|
|
for op in self.ops[op_start:op_stop]: |
|
|
|
|
|
|
|
# if we fork-strip, always take the stems |
|
|
|
|
|
|
|
i0_idx = stems[op[2]] if stems[op[2]] >= 0 else op[2] |
|
|
|
|
|
|
|
i1_idx = stems[op[3]] if stems[op[3]] >= 0 else op[3] |
|
|
|
|
|
|
|
ref_count[i0_idx] -= 1 |
|
|
|
|
|
|
|
ref_count[i1_idx] -= 1 |
|
|
|
|
|
|
|
if ref_count[i0_idx] <= 0: free_list.append(self.sat[i0_idx, 0]) |
|
|
|
|
|
|
|
if ref_count[i1_idx] <= 0: free_list.append(self.sat[i1_idx, 0]) |
|
|
|
|
|
|
|
o_idx = op[1] |
|
|
|
|
|
|
|
cap = wavecaps[o_idx] |
|
|
|
|
|
|
|
self.sat[o_idx] = h.alloc(cap), cap, 0 |
|
|
|
|
|
|
|
if not keep_waveforms: |
|
|
|
|
|
|
|
for loc in free_list: |
|
|
|
|
|
|
|
h.free(loc) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# copy memory location and capacity from stems to fanout lines |
|
|
|
|
|
|
|
for lidx, stem in enumerate(stems): |
|
|
|
|
|
|
|
if stem >= 0: # if at a fanout line |
|
|
|
|
|
|
|
self.sat[lidx] = self.sat[stem] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# copy memory location to PO/PPO area |
|
|
|
|
|
|
|
for i, n in enumerate(self.interface): |
|
|
|
|
|
|
|
if len(n.ins) > 0: |
|
|
|
|
|
|
|
self.sat[self.ppo_offset + i] = self.sat[n.ins[0].index] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# pad timing |
|
|
|
|
|
|
|
self.timing = np.zeros((self.sat_length, 2, 2)) |
|
|
|
|
|
|
|
self.timing[:len(timing)] = timing |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# allocate self.state |
|
|
|
|
|
|
|
self.state = np.zeros((h.max_size, sims), dtype='float32') + TMAX |
|
|
|
|
|
|
|
|
|
|
|
m1 = np.array([2 ** x for x in range(7, -1, -1)], dtype='uint8') |
|
|
|
m1 = np.array([2 ** x for x in range(7, -1, -1)], dtype='uint8') |
|
|
|
m0 = ~m1 |
|
|
|
m0 = ~m1 |
|
|
|
self.mask = np.rollaxis(np.vstack((m0, m1)), 1) |
|
|
|
self.mask = np.rollaxis(np.vstack((m0, m1)), 1) |
|
|
|