Source code for akida.virtual_devices

from math import ceil, floor, sqrt
from .core import Device, IpVersion, NSoC_v1, NSoC_v2, TwoNodesIP_v1, AKD1500_v1, FPGA_v2, NP


[docs] def AKD1000(): """Returns a virtual device for an AKD1000 NSoC. This function returns a virtual device for the Brainchip's AKD1000 NSoC. Returns: :obj:`Device`: a virtual device. """ dma_event = NP.Ident(3, 1, 0) dma_conf = NP.Ident(3, 1, 1) nps = [ NP.Info(NP.Ident(1, 3, 0), {NP.Type.CNP1, NP.Type.FNP3}), NP.Info(NP.Ident(1, 3, 1), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(1, 3, 2), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(1, 3, 3), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(1, 4, 0), {NP.Type.CNP1, NP.Type.FNP3}), NP.Info(NP.Ident(1, 4, 1), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(1, 4, 2), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(1, 4, 3), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(1, 5, 0), {NP.Type.CNP1, NP.Type.FNP3}), NP.Info(NP.Ident(1, 5, 1), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(1, 5, 2), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(1, 5, 3), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(2, 3, 0), {NP.Type.CNP1, NP.Type.FNP3}), NP.Info(NP.Ident(2, 3, 1), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(2, 3, 2), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(2, 3, 3), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(2, 4, 0), {NP.Type.CNP1, NP.Type.FNP3}), NP.Info(NP.Ident(2, 4, 1), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(2, 4, 2), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(2, 4, 3), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(2, 5, 0), {NP.Type.CNP1, NP.Type.FNP3}), NP.Info(NP.Ident(2, 5, 1), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(2, 5, 2), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(2, 5, 3), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(3, 1, 2), {NP.Type.CNP1}), NP.Info(NP.Ident(3, 1, 3), {NP.Type.CNP1}), NP.Info(NP.Ident(3, 2, 0), {NP.Type.CNP1, NP.Type.FNP3}), NP.Info(NP.Ident(3, 2, 1), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(3, 2, 2), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(3, 2, 3), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(3, 3, 0), {NP.Type.CNP1, NP.Type.FNP3}), NP.Info(NP.Ident(3, 3, 1), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(3, 3, 2), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(3, 3, 3), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(3, 4, 0), {NP.Type.CNP1, NP.Type.FNP3}), NP.Info(NP.Ident(3, 4, 1), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(3, 4, 2), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(3, 4, 3), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(3, 5, 0), {NP.Type.CNP1, NP.Type.FNP3}), NP.Info(NP.Ident(3, 5, 1), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(3, 5, 2), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(3, 5, 3), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(4, 1, 0), {NP.Type.CNP1, NP.Type.FNP2}), NP.Info(NP.Ident(4, 1, 1), {NP.Type.CNP1, NP.Type.FNP2}), NP.Info(NP.Ident(4, 1, 2), {NP.Type.CNP1, NP.Type.FNP2}), NP.Info(NP.Ident(4, 1, 3), {NP.Type.CNP1, NP.Type.FNP2}), NP.Info(NP.Ident(4, 2, 0), {NP.Type.CNP1, NP.Type.FNP3}), NP.Info(NP.Ident(4, 2, 1), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(4, 2, 2), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(4, 2, 3), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(4, 3, 0), {NP.Type.CNP1, NP.Type.FNP3}), NP.Info(NP.Ident(4, 3, 1), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(4, 3, 2), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(4, 3, 3), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(4, 4, 0), {NP.Type.CNP1, NP.Type.FNP3}), NP.Info(NP.Ident(4, 4, 1), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(4, 4, 2), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(4, 4, 3), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(4, 5, 0), {NP.Type.CNP1, NP.Type.FNP3}), NP.Info(NP.Ident(4, 5, 1), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(4, 5, 2), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(4, 5, 3), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(5, 2, 0), {NP.Type.CNP1, NP.Type.FNP3}), NP.Info(NP.Ident(5, 2, 1), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(5, 2, 2), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(5, 2, 3), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(5, 3, 0), {NP.Type.CNP1, NP.Type.FNP3}), NP.Info(NP.Ident(5, 3, 1), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(5, 3, 2), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(5, 3, 3), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(5, 4, 0), {NP.Type.CNP1, NP.Type.FNP3}), NP.Info(NP.Ident(5, 4, 1), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(5, 4, 2), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(5, 4, 3), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(5, 5, 0), {NP.Type.CNP1, NP.Type.FNP3}), NP.Info(NP.Ident(5, 5, 1), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(5, 5, 2), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(5, 5, 3), {NP.Type.CNP1, NP.Type.CNP2}) ] mesh = NP.Mesh(IpVersion.v1, dma_event, dma_conf, nps) return Device(NSoC_v2, mesh)
[docs] def TwoNodesIPv1(): """Returns a virtual device for a two nodes Akida IP. Returns: :obj:`Device`: a virtual device. """ dma_event = NP.Ident(1, 1, 0) dma_conf = NP.Ident(1, 1, 1) nps = [ NP.Info(NP.Ident(1, 2, 0), {NP.Type.CNP1, NP.Type.FNP2}), NP.Info(NP.Ident(1, 2, 1), {NP.Type.CNP1}), NP.Info(NP.Ident(1, 2, 2), {NP.Type.CNP1}), NP.Info(NP.Ident(1, 2, 3), {NP.Type.CNP1}), NP.Info(NP.Ident(1, 3, 0), {NP.Type.CNP1, NP.Type.FNP3}), NP.Info(NP.Ident(1, 3, 1), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(1, 3, 2), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(1, 3, 3), {NP.Type.CNP1, NP.Type.CNP2}) ] mesh = NP.Mesh(IpVersion.v1, dma_event, dma_conf, nps) return Device(TwoNodesIP_v1, mesh)
[docs] def AKD1500(): """Returns a virtual device for AKD1500 chip. Returns: :obj:`Device`: a virtual device. """ dma_event = NP.Ident(1, 1, 0) dma_conf = NP.Ident(1, 1, 1) nps = [ NP.Info(NP.Ident(1, 2, 0), {NP.Type.CNP1, NP.Type.FNP2}), NP.Info(NP.Ident(1, 2, 1), {NP.Type.CNP1}), NP.Info(NP.Ident(1, 2, 2), {NP.Type.CNP1}), NP.Info(NP.Ident(1, 2, 3), {NP.Type.CNP1}), NP.Info(NP.Ident(1, 3, 0), {NP.Type.CNP1, NP.Type.FNP3}), NP.Info(NP.Ident(1, 3, 1), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(1, 3, 2), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(1, 3, 3), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(2, 1, 0), {NP.Type.CNP1, NP.Type.FNP3}), NP.Info(NP.Ident(2, 1, 1), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(2, 1, 2), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(2, 1, 3), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(2, 2, 0), {NP.Type.CNP1, NP.Type.FNP3}), NP.Info(NP.Ident(2, 2, 1), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(2, 2, 2), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(2, 2, 3), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(2, 3, 0), {NP.Type.CNP1, NP.Type.FNP3}), NP.Info(NP.Ident(2, 3, 1), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(2, 3, 2), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(2, 3, 3), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(3, 1, 0), {NP.Type.CNP1, NP.Type.FNP3}), NP.Info(NP.Ident(3, 1, 1), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(3, 1, 2), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(3, 1, 3), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(3, 2, 0), {NP.Type.CNP1, NP.Type.FNP3}), NP.Info(NP.Ident(3, 2, 1), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(3, 2, 2), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(3, 2, 3), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(3, 3, 0), {NP.Type.CNP1, NP.Type.FNP3}), NP.Info(NP.Ident(3, 3, 1), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(3, 3, 2), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(3, 3, 3), {NP.Type.CNP1, NP.Type.CNP2}) ] mesh = NP.Mesh(IpVersion.v1, dma_event, dma_conf, nps) return Device(AKD1500_v1, mesh)
[docs] def TwoNodesIPv2(): """Returns a 2-node virtual device for FPGA v2. Returns: :obj:`Device`: a virtual device. """ dma_event = NP.Ident(1, 1, 0) dma_conf = NP.Ident(1, 1, 1) skipdmas_num_channels = 2 skip_dmas = [ NP.Info( NP.Ident(1, 1, 3, skipdmas_num_channels), {NP.Type.SKIP_DMA_STORE, NP.Type.SKIP_DMA_LOAD})] nps = [ NP.Info(NP.Ident(1, 2, 0), {NP.Type.CNP1, NP.Type.FNP2}), NP.Info(NP.Ident(1, 2, 1), {NP.Type.CNP1}), NP.Info(NP.Ident(1, 2, 2), {NP.Type.CNP1}), NP.Info(NP.Ident(1, 2, 3), {NP.Type.CNP1}), NP.Info(NP.Ident(2, 2, 0), {NP.Type.CNP1, NP.Type.FNP3}), NP.Info(NP.Ident(2, 2, 1), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(2, 2, 2), {NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(2, 2, 3), {NP.Type.CNP1, NP.Type.CNP2}) ] mesh = NP.Mesh(IpVersion.v2, dma_event, dma_conf, nps, skip_dmas) return Device(FPGA_v2, mesh)
[docs] def SixNodesIPv2(): """Returns a 6-node virtual device for FPGA v2. Returns: :obj:`Device`: a virtual device. """ dma_event = NP.Ident(1, 1, 0) dma_conf = NP.Ident(1, 1, 1) skipdmas_num_channels = 4 skip_dmas = [ NP.Info( NP.Ident(1, 1, 3, skipdmas_num_channels), {NP.Type.SKIP_DMA_STORE, NP.Type.SKIP_DMA_LOAD})] nps = [ NP.Info(NP.Ident(1, 2, 0), {NP.Type.TNP_B, NP.Type.CNP1, NP.Type.FNP2}), NP.Info(NP.Ident(1, 2, 1), {NP.Type.TNP_B, NP.Type.CNP1}), NP.Info(NP.Ident(1, 2, 2), {NP.Type.TNP_B, NP.Type.CNP1}), NP.Info(NP.Ident(1, 2, 3), {NP.Type.TNP_B, NP.Type.CNP1}), NP.Info(NP.Ident(1, 3, 0), {NP.Type.TNP_B, NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(1, 3, 1), {NP.Type.TNP_B, NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(1, 3, 2), {NP.Type.TNP_B, NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(1, 3, 3), {NP.Type.TNP_B, NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(2, 2, 0), {NP.Type.TNP_B, NP.Type.CNP1, NP.Type.FNP3}), NP.Info(NP.Ident(2, 2, 1), {NP.Type.TNP_B, NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(2, 2, 2), {NP.Type.TNP_B, NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(2, 2, 3), {NP.Type.TNP_B, NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(2, 3, 0), {NP.Type.TNP_B, NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(2, 3, 1), {NP.Type.TNP_B, NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(2, 3, 2), {NP.Type.TNP_B, NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(2, 3, 3), {NP.Type.TNP_B, NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(3, 2, 0), {NP.Type.TNP_B, NP.Type.CNP1, NP.Type.FNP3}), NP.Info(NP.Ident(3, 2, 1), {NP.Type.TNP_B, NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(3, 2, 2), {NP.Type.TNP_B, NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(3, 2, 3), {NP.Type.TNP_B, NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(3, 3, 0), {NP.Type.TNP_B, NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(3, 3, 1), {NP.Type.TNP_B, NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(3, 3, 2), {NP.Type.TNP_B, NP.Type.CNP1, NP.Type.CNP2}), NP.Info(NP.Ident(3, 3, 3), {NP.Type.TNP_B, NP.Type.CNP1, NP.Type.CNP2}) ] mesh = NP.Mesh(IpVersion.v2, dma_event, dma_conf, nps, skip_dmas) return Device(FPGA_v2, mesh)
[docs] def create_device(num_cnp_tnp, num_fnp, num_skip_dma_channel=0, include_hrc=True, input_buffer_memory=None, weight_memory=None, hw_version=FPGA_v2, ): """Creates an Akida device with the specified hardware components. Args: num_cnp_tnp (int): Number of CNP and TNP_B units (TNP_B is only available on 2.x devices). num_fnp (int): Number of FNP units to include. An FNP2 with external memory is added first, followed by FNP3 units. num_skip_dma_channel (int, optional): Number of skip DMA channels (only applicable for 2.x devices). Defaults to 0. include_hrc (bool, optional): Whether to include the HRC. Defaults to True. input_buffer_memory (int, optional): Size of shared input packet SRAM in bytes available inside the mesh for each two NPs. Defaults to None. weight_memory (int, optional): Size of shared filter SRAM in bytes available inside the mesh for each two NPs. Defaults to None. hw_version (akida.HwVersion, optional): The version of the device. Defaults to FPGA_v2. Returns: akida.Device: An Akida device. """ # General akida node info SKIP_DMA_ROW = 1 SKIP_DMA_ID = 3 MAX_SKIP_DMA_CHANNELS_PER_COL = 4 NUM_NPS_PER_NODE = 4 class V1_INFO: INPUT_BUFFER_MEMORY = 43008 WEIGHT_MEMORY = 44800 HW_VERSIONS = [NSoC_v1, NSoC_v2, TwoNodesIP_v1, AKD1500_v1] class V2_INFO: INPUT_BUFFER_MEMORY = 65536 WEIGHT_MEMORY = 51200 HW_VERSIONS = [FPGA_v2] def _get_memory_info(input_buffer_memory, weight_memory, hw_info): if input_buffer_memory is None: input_buffer_memory = hw_info.INPUT_BUFFER_MEMORY if weight_memory is None: weight_memory = hw_info.WEIGHT_MEMORY return input_buffer_memory, weight_memory def _compute_total_nps(num_cnp_tnp, num_fnp): total_nps = num_cnp_tnp + num_fnp # The nodes are completed with NPs of type CNP1, CNP2 (and TNP_B if hw_version = FPGA_v2) # if the requested NPs are not a multiple of NUM_NPS_PER_NODE. nps_to_add = (-total_nps) % NUM_NPS_PER_NODE num_cnp_tnp += nps_to_add total_nps += nps_to_add return total_nps, num_cnp_tnp def _compute_optimal_nps_grid_shape(total_nps): if total_nps == 0: return 0, 0 num_nodes = total_nps / NUM_NPS_PER_NODE fractional_diff = (num_nodes / sqrt(num_nodes)) - (num_nodes // sqrt(num_nodes)) # Increment columns first and then rows num_cols = floor(sqrt(num_nodes)) + ceil(fractional_diff) num_rows = floor(sqrt(num_nodes)) + round(fractional_diff) return num_rows, num_cols def _make_skip_dmas(num_cols, num_skip_dma_channel): skip_dmas = [] if hw_version != FPGA_v2 and num_skip_dma_channel > 0: raise ValueError(f"Skip DMAs are only supported on v2 devices (hw_version=FPGA_v2). " f"Current hardware version: {hw_version}.") if num_skip_dma_channel == 0: return skip_dmas # Distribute Skip DMAs accross columns as much as possible # When the number of Skip DMAs exceeds the number of columns used by nps, we increase # the number of columns. current_max_skip_dma_channels = num_cols * MAX_SKIP_DMA_CHANNELS_PER_COL if (extra_channels := num_skip_dma_channel - current_max_skip_dma_channels) > 0: num_cols += ceil((extra_channels) / MAX_SKIP_DMA_CHANNELS_PER_COL) # Compute number of channels per skip dma num_channels_per_skip_dma = ceil(num_skip_dma_channel / num_cols) # Deduce the number of skip dmas in the device num_skip_dmas = min(num_skip_dma_channel, num_cols) for col in range(1, num_skip_dmas + 1): skip_dmas.append( NP.Info( NP.Ident(col, SKIP_DMA_ROW, SKIP_DMA_ID, num_channels_per_skip_dma), {NP.Type.SKIP_DMA_STORE, NP.Type.SKIP_DMA_LOAD}, ) ) return skip_dmas def _make_nps(num_rows, num_cols, num_cnp_tnp, num_fnp): # Construct NP types cnp_types = [NP.Type.CNP1, NP.Type.CNP2] fnp_types = [NP.Type.CNP1, NP.Type.CNP2, NP.Type.FNP2] if hw_version == FPGA_v2: cnp_types.insert(0, NP.Type.TNP_B) fnp_types.insert(0, NP.Type.TNP_B) nps = [] # Starting from row 2 for row in range(2, num_rows + 2): for col in range(1, num_cols + 1): # Now loop over nps for id in range(NUM_NPS_PER_NODE): if num_cnp_tnp > 0: nps.append(NP.Info(NP.Ident(col, row, id), cnp_types)) num_cnp_tnp -= 1 elif num_fnp > 0: nps.append(NP.Info(NP.Ident(col, row, id), fnp_types)) # Change FNP2 with FNP3 if fnp_types[-1] == NP.Type.FNP2: fnp_types[-1] = NP.Type.FNP3 num_fnp -= 1 return nps if not include_hrc: raise NotImplementedError("Disabling HRC is not supported.") # Get Ip version ip_version = IpVersion.v2 if hw_version.product_id == 0xA2 else IpVersion.v1 # Check HW version hw_info = V2_INFO if ip_version == IpVersion.v2 else V1_INFO if hw_version not in hw_info.HW_VERSIONS: raise ValueError(f"Invalid HW version '{hw_version}'. " f"Expected one of: {hw_info.HW_VERSIONS}.") # Fill input and weight memory with defaults if necessary input_buffer_memory, weight_memory = _get_memory_info(input_buffer_memory, weight_memory, hw_info) # Compute total nps and construct the optimal grid for NPs # The mesh should be as square as possible total_nps, num_cnp_tnp = _compute_total_nps(num_cnp_tnp, num_fnp) num_rows, num_cols = _compute_optimal_nps_grid_shape(total_nps) if total_nps == 0 and not include_hrc: raise ValueError("It is not possible to create a completely empty device. " f"num_cnp_tnp + num_fnp ({total_nps}) must be greater than zero or " "HRC must be included).") # Make DMA event and conf dma_event = NP.Ident(1, 1, 0) dma_conf = NP.Ident(1, 1, 1) # Make SkipDMAs skip_dmas = _make_skip_dmas(num_cols, num_skip_dma_channel) # Make NPs nps = _make_nps(num_rows, num_cols, num_cnp_tnp, num_fnp) # Make the mesh mesh = NP.Mesh(ip_version, dma_event, dma_conf, nps, skip_dmas, input_buffer_memory, weight_memory) return Device(hw_version, mesh)