Skip to content

Commit

Permalink
add nnsim
Browse files Browse the repository at this point in the history
  • Loading branch information
vysarge committed Nov 25, 2017
1 parent 79da69b commit c54519b
Show file tree
Hide file tree
Showing 9 changed files with 504 additions and 0 deletions.
Empty file added nnsim/__init__.py
Empty file.
53 changes: 53 additions & 0 deletions nnsim/channel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from nnsim.module import Module, HWError
from nnsim.reg import Reg

class ChannelError(HWError):
pass

class Channel(Module):
def instantiate(self, depth=2):
self.data = [None]*depth
self.depth = depth

self.rd_ptr = Reg(0)
self.wr_ptr = Reg(0)

def peek(self, idx=0):
if not self.valid(idx):
raise ChannelError("Reading from empty channel")
return self.data[(self.rd_ptr.rd() + idx) % self.depth]

def push(self, x):
if not self.vacancy():
raise ChannelError("Enqueueing into full channel")
self.data[self.wr_ptr.rd() % self.depth] = x
self.wr_ptr.wr((self.wr_ptr.rd() + 1) % (2*self.depth))

def free(self, count=1):
if not self.valid(count-1):
raise ChannelError("Dequeueing from empty channel")
self.rd_ptr.wr((self.rd_ptr.rd() + count) % (2*self.depth))

def pop(self):
self.free(1)
return self.peek(0)

def valid(self, idx=0):
return ((self.wr_ptr.rd() - self.rd_ptr.rd()) % (2*self.depth)) > idx

def vacancy(self, idx=0):
return ((self.rd_ptr.rd() + self.depth - self.wr_ptr.rd()) %
(2*self.depth)) > idx

def clear(self):
# Use with care since it conflicts with enq and deq
self.rd_ptr.wr(self.wr_ptr.rd())

def EmptyChannel(Channel):
def valid(self, idx=0):
return False

def FullChannel(Channel):
def vacancy(self):
return False

70 changes: 70 additions & 0 deletions nnsim/costs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
from nnsim.module import HWError
from collections import defaultdict

class CostError(HWError):
pass

# default values for 65nm process
# ALU:1, RF:1, PE/LN:2, GB:6, DRAM:200

# Track number of accesses, and accumulate total energy (normalized over ALU)
class CostModel:
def init(self,bitwidth=32,registers=128,num_pe=200,buffer_kb=100):
self.uses=defaultdict(int)

self.cost=defaultdict(int)
self.ALU(bitwidth)
self.RegisterFile(registers, bitwidth)
self.LocalNetwork(num_pe)
self.GlobalBuffer(buffer_kb*1024)
self.DRAM(num_pe)

def count(self, event, count=1):
self.uses[event] += count

def ALU(self, bitwidth=32):
self.cost["ALU"] = 1


def RegisterFile(self, registers=128, bitwidth=32):
"""RF register file energy
Dependent on the number of registers.
If within 0.5 - 1kB total assume 1x ALU"""

bytes = registers * bitwidth / 8
if bytes > 1024:
raise CostError("Estimate RF cost if you need a larger register file")
self.cost["RF"] = 1 # RF -> PE
# register read/write

# PE - Local PE network
# assume talking to neighbors only
# for 200-1000 PEs total
def LocalNetwork(self, num_pes = 200):
if num_pes > 1000:
raise CostError("Estimate cost if you need a large number of PEs")
self.cost["LN"] = 2 # PE -> PE

# GB Global Buffer
# assume all PEs have direct access
# no dynamic network
# for 100-500kB assume 6x
def GlobalBuffer(self, bytes = 100*1024):
if bytes > 500*1024:
raise CostError("Estimate cost if you need a larger Global Buffer")
self.cost["GB"] = 6 # GB -> PE

def DRAM(self, controllers=1):
self.cost["DRAM"] = 200 # DRAM -> GB

def energy(self, verbose=True):
"""Tally up energy consumption"""
total=0
for c in self.uses:
energy = self.cost[c] * self.uses[c]
if verbose:
print("%s\t%d\t%d\t%d\n" % (c, self.cost[c], self.uses[c], energy))
total += energy
if verbose:
print("%s\t \t\t%d\n" % ("Total", total))
return total
86 changes: 86 additions & 0 deletions nnsim/fifo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
from nnsim.module import Module, HWError
from nnsim.reg import Reg

class FIFOError(HWError):
pass

class FIFO(Module):
def instantiate(self, depth=2):
self.data = [0]*depth
self.depth = depth

self.rd_ptr = Reg(0)
self.wr_ptr = Reg(0)

def peek(self):
if self.wr_ptr.rd() == self.rd_ptr.rd():
raise FIFOError("Reading from empty FIFO")
return self.data[self.rd_ptr.rd() % self.depth]

def enq(self, x):
if (self.wr_ptr.rd() - self.rd_ptr.rd()) % (2*self.depth) == self.depth:
raise FIFOError("Enqueueing into full FIFO")
self.data[self.wr_ptr.rd() % self.depth] = x
self.wr_ptr.wr((self.wr_ptr.rd() + 1) % (2*self.depth))

def deq(self):
if self.wr_ptr.rd() == self.rd_ptr.rd():
raise FIFOError("Dequeueing from empty FIFO")
self.rd_ptr.wr((self.rd_ptr.rd() + 1) % (2*self.depth))

def not_full(self):
return not ((self.wr_ptr.rd() - self.rd_ptr.rd()) % (2*self.depth) == self.depth)

def not_empty(self):
return not (self.wr_ptr.rd() == self.rd_ptr.rd())

def reset(self):
self.rd_ptr.wr(self.wr_ptr.rd())

class WindowFIFO(Module):
def instantiate(self, depth, peek_window, enq_window, deq_window):
self.data = [0]*depth
self.depth = depth
self.peek_window = peek_window
self.enq_window = enq_window
self.deq_window = deq_window

self.rd_ptr = Reg(0)
self.wr_ptr = Reg(0)

def peek(self):
if (self.wr_ptr.rd() - self.rd_ptr.rd()) % (2*self.depth) \
< self.peek_window:
raise FIFOError("Reading from empty FIFO")
peek_output = [0]*self.peek_window
for i in xrange(self.peek_window):
peek_output[i] = self.data[(self.rd_ptr.rd() + i)% self.depth]
return peek_output

def enq(self, x):
if (self.wr_ptr.rd() - self.rd_ptr.rd() + self.enq_window - 1) % \
(2*self.depth) >= self.depth:
raise FIFOError("Enqueueing into full FIFO")
for i in xrange(self.enq_window):
self.data[(self.wr_ptr.rd() + i) % self.depth] = x[i]
self.wr_ptr.wr((self.wr_ptr.rd() + self.enq_window) % (2*self.depth))

def deq(self):
if (self.wr_ptr.rd() - self.rd_ptr.rd()) % (2*self.depth) \
< self.deq_window:
raise FIFOError("Dequeueing from empty FIFO")
self.rd_ptr.wr((self.rd_ptr.rd() + self.deq_window) % (2*self.depth))

def not_full(self):
return not ((self.wr_ptr.rd() - self.rd_ptr.rd() + self.enq_window - 1) %
(2*self.depth) >= self.depth)

def valid(self):
return not ((self.wr_ptr.rd() - self.rd_ptr.rd()) % (2*self.depth)
< self.peek_window)

def not_empty(self):
return not ((self.wr_ptr.rd() - self.rd_ptr.rd()) % (2*self.depth) < self.deq_window)

def clear(self):
self.rd_ptr.wr(self.wr_ptr.rd())
100 changes: 100 additions & 0 deletions nnsim/module.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Module object

class HWError(Exception):
pass

class Module(object):
def __init__(self, *args, **kwargs):
self.name = str(id(self))
self.path = ""
self.sub_modules = []

self.stat_type = 'hide'
self.raw_stats = {}
self.final_stats = {}

self.instantiate(*args, **kwargs)
self.register_modules()

def setup(self):
pass

def __setup__(self, path=''):
self.setup()
self.path = "%s/%s" % (path, self.name)
for sub_module in self.sub_modules:
sub_module.__setup__(self.path)

def instantiate(self):
raise HWError("Cannot instantiate abstract module")

def finalize_stats(self):
self.final_stats = { k:v for k, v in self.raw_stats.items() }
for sub_module in self.sub_modules:
sub_module.finalize_stats()
if sub_module.stat_type == 'aggregate':
for key in sub_module.final_stats:
if key in self.final_stats:
self.final_stats[key] += sub_module.final_stats[key]
else:
self.final_stats[key] = sub_module.final_stats[key]

def dump_stats(self):
if self.stat_type == 'show':
print("%s: " % self.path)
for key in self.final_stats:
print("\t%s: %s" % (key, self.final_stats[key]))
for sub_module in self.sub_modules:
sub_module.dump_stats()

def register_modules(self):
for attr in vars(self).values():
if issubclass(type(attr), Module):
self.sub_modules.append(attr)
elif issubclass(type(attr), ModuleList):
self.sub_modules += attr.register()

def tick(self):
pass

def reset(self):
pass

def __tick__(self):
self.tick()
for sub_module in self.sub_modules:
sub_module.__tick__()

def __ntick__(self):
for sub_module in self.sub_modules:
sub_module.__ntick__()

def __reset__(self):
self.reset()
for sub_module in self.sub_modules:
sub_module.__reset__()

class ModuleList(object):
def __init__(self):
self.list = []

def append(self, m):
if issubclass(type(m), Module) or issubclass(type(m), ModuleList):
self.list.append(m)
else:
raise HWError("Can only append Module or ModuleList")

def __getitem__(self, key):
return self.list[key]


def register(self):
module_list = []
for m in self.list:
if issubclass(type(m), Module):
module_list.append(m)
elif issubclass(type(m), ModuleList):
module_list += m.register()
return module_list


29 changes: 29 additions & 0 deletions nnsim/pipeline.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from nnsim.fifo import FIFO

class Pipeline(Module):
def instantiate(self, stages=2):
self.stages = []
for stage in xrange(stages):
self.stages.append(FIFO(depth=1))

def rd(self):
if not self.rd_rdy():
raise FIFOError("Pipeline not ready for read")
self.stages[-1].deq()
return self.stages[-1].peek()

def wr(self, x):
if not self.wr_rdy():
raise FIFOError("Pipeline not ready for write")
self.stages[0].enq(x)

def deq(self):
if self.wr_ptr.rd() == self.rd_ptr.rd():
raise FIFOError("Dequeueing from empty FIFO")
self.rd_ptr.wr((self.rd_ptr.rd() + 1) % (2*self.depth))

def wr_rdy(self):
return self.stages[0].not_full()

def rd_rdy(self):
return self.stages[-1].not_empty()
Loading

0 comments on commit c54519b

Please sign in to comment.