diff --git a/fpgaconvnet/models/layers/IOPortLayer.py b/fpgaconvnet/models/layers/IOPortLayer.py new file mode 100644 index 0000000..1ccf81f --- /dev/null +++ b/fpgaconvnet/models/layers/IOPortLayer.py @@ -0,0 +1,135 @@ +import pydot +import numpy as np + +from fpgaconvnet.data_types import FixedPoint + +from fpgaconvnet.models.layers import Layer +from fpgaconvnet.models.modules import IOPort + +class IOPortLayer(Layer): + def __init__( + self, + rows: int, + cols: int, + channels: int, + coarse: int, + direction: str, + connectivity: str = "off-chip", + dma_stream_width: int = 128, + dma_burst_size: int = 256, + dma_clock_freq: int = 1067, + data_t: FixedPoint = FixedPoint(16,8), + backend: str = "chisel", + regression_model: str = "linear_regression" + ): + + # initialise parent class + super().__init__(rows, cols, channels, + coarse, coarse, data_t=data_t) + + # save parameters + self._coarse = coarse + self.direction = direction + self.dma_stream_width = dma_stream_width + self.dma_burst_size = dma_burst_size + self.dma_clock_freq = dma_clock_freq + self.connectivity = connectivity + + # direction + assert direction in ["in", "out"], f"{direction} is an invalid direction" + + # connectivity type + assert connectivity in ["on-chip", "off-chip"], f"{connectivity} is an invalid connectivity type" + + # backend flag + assert backend in ["chisel"], f"{backend} is an invalid backend" + self.backend = backend + + # regression model + assert regression_model in ["linear_regression", "xgboost"], f"{regression_model} is an invalid regression model" + self.regression_model = regression_model + + self.available_bw = self.mem_bw_in if self.direction == "in" else self.mem_bw_out + + # initialise modules + self.modules["ioport"] = IOPort(self.rows, self.cols, + self.channels, self._coarse, self.available_bw, self.direction, self.dma_stream_width, self.dma_burst_size, backend=self.backend, regression_model=self.regression_model) + + self.update() + + @property + def coarse(self) -> int: + return self._coarse + + @property + def coarse_in(self) -> int: + return self._coarse + + @property + def coarse_out(self) -> int: + return self._coarse + + def streams_in(self) -> int: + return 1 if self.direction == "in" else self.coarse_in + + def streams_out(self) -> int: + return 1 if self.direction == "out" else self.coarse_out + + def width_in(self) -> int: + return self.dma_stream_width if self.direction == "in" else self.data_t.width + + def width_out(self) -> int: + return self.dma_stream_width if self.direction == "out" else self.data_t.width + + def layer_info(self, parameters, batch_size=1): + Layer.layer_info(self, parameters, batch_size) + + def update(self): + self.modules["ioport"].rows = self.rows + self.modules["ioport"].cols = self.cols + self.modules["ioport"].channels = self.channels + self.modules["ioport"].num_ports = self.coarse + self.modules["ioport"].mem_bw = self.mem_bw_in if self.direction == "in" else self.mem_bw_out + self.modules["ioport"].dma_stream_width = self.dma_stream_width + self.modules["ioport"].dma_burst_size = self.dma_burst_size + self.modules["ioport"].data_width = self.data_t.width + + def resource(self): + + # get ioport resources + ioport_rsc = self.modules['ioport'].rsc() + + # Total + return { + "LUT" : ioport_rsc['LUT'], + "FF" : ioport_rsc['FF'], + "BRAM" : ioport_rsc['BRAM'], + "DSP" : ioport_rsc['DSP'], + } + + def visualise(self,name): + + # create layer cluster + cluster = pydot.Cluster(name, label=name, + style="dashed", bgcolor="moccasin") + + # add ioport module + ioport_name = "_".join([name,"ioport"]) + cluster.add_node(self.modules["ioport"].visualise(ioport_name)) + + # get nodes in and out + nodes_in = [ ioport_name for i in range(self.streams_in()) ] + nodes_out = [ ioport_name for i in range(self.streams_out()) ] + + # return module + return cluster, nodes_in, nodes_out + + def functional_model(self, data, batch_size=1): + + assert data.shape[0] == self.rows , "ERROR: invalid row dimension" + assert data.shape[1] == self.cols , "ERROR: invalid column dimension" + assert data.shape[2] == self.channels, "ERROR: invalid channel dimension" + + # return output featuremap + return data + diff --git a/fpgaconvnet/models/layers/__init__.py b/fpgaconvnet/models/layers/__init__.py index 0044e66..ae67177 100644 --- a/fpgaconvnet/models/layers/__init__.py +++ b/fpgaconvnet/models/layers/__init__.py @@ -9,6 +9,8 @@ from .Layer import Layer from .MultiPortLayer import MultiPortLayer +from .IOPortLayer import IOPortLayer + from .BatchNormLayer import BatchNormLayer from .InnerProductLayer import InnerProductLayer from .InnerProductSparseLayer import InnerProductSparseLayer diff --git a/fpgaconvnet/models/modules/IOPort.py b/fpgaconvnet/models/modules/IOPort.py new file mode 100644 index 0000000..6926078 --- /dev/null +++ b/fpgaconvnet/models/modules/IOPort.py @@ -0,0 +1,76 @@ +from dataclasses import dataclass, field + +from fpgaconvnet.models.modules import Module, MODULE_FONTSIZE +import pydot +import numpy as np + +@dataclass +class IOPort(Module): + num_ports: int + mem_bw: float + direction: str + dma_stream_width: int + dma_burst_size: int + backend: str = "chisel" + regression_model: str = "linear_regression" + + @property + def port_width(self) -> int: + return self.data_width*self.num_ports + + def module_info(self): + # get the base module fields + info = Module.module_info(self) + # add module-specific info fields + info["num_ports"] = self.num_ports + info["mem_bw"] = self.mem_bw + info["dma_stream_width"] = self.dma_stream_width + info["dma_burst_size"] = self.dma_burst_size + # return the info + return info + + def utilisation_model(self): + # TODO we should probably use self.dma_stream_width and self.dma_burst_size to calculate the DMA's resource usage + return { + "LUT" : np.array([0]), + "FF" : np.array([0]), + "DSP" : np.array([0]), + "BRAM" : np.array([0]), + } + + def rate_in(self): + # TODO: should this be 1 on else or infinite? + return self.mem_bw if self.direction == "in" else 1 + + def rate_out(self): + # TODO: should this be 1 on else or infinite? + return self.mem_bw if self.direction == "out" else 1 + + def get_pred_array(self): + pass + + def visualise(self, name): + return pydot.Node(name, label="io_port", shape="polygon", + sides=4, style="filled", fillcolor="olive", fontsize=MODULE_FONTSIZE) + + def functional_model(self, data): + return data + + def rsc(self, coef=None, model=None): + # get the linear model estimation + # rsc = Module.rsc(self, coef, model) + + if self.regression_model == "linear_regression": + pass # TODO + elif self.regression_model == "xgboost" or self.regression_model == "xgboost-kernel": + pass # TODO + + rsc = {} + # got theses values from averaging the resource usage of the DMA's across various Vivado projects on ZCU106 device. It definitely needs to be refined. + rsc['LUT'] = 1160 + rsc['FF'] = 1805 + rsc['BRAM'] = 17 + rsc['DSP']= 0 + + # return the resource usage + return rsc diff --git a/fpgaconvnet/models/modules/__init__.py b/fpgaconvnet/models/modules/__init__.py index 6ff88fb..70a1d06 100644 --- a/fpgaconvnet/models/modules/__init__.py +++ b/fpgaconvnet/models/modules/__init__.py @@ -14,6 +14,7 @@ def int2bits(n): return math.ceil(math.log(n, 2)) from .Module import Module +from .IOPort import IOPort from .Accum import Accum from .ShiftScale import ShiftScale from .Conv import Conv