blob: 7b6c3bed0e4bf8fd6ae3ff7e3c71b7e72152adcc [file] [log] [blame]
Tim Hall79d07d22020-04-27 18:20:16 +01001# Copyright (C) 2020 Arm Limited or its affiliates. All rights reserved.
2#
3# SPDX-License-Identifier: Apache-2.0
4#
5# Licensed under the Apache License, Version 2.0 (the License); you may
6# not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an AS IS BASIS, WITHOUT
13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
Tim Hall79d07d22020-04-27 18:20:16 +010016# Description:
Tim Hallc8a73862020-10-27 12:43:14 +000017# Holds a container for Ethos-U and System architecture parameters.
Diego Russoea6111a2020-04-14 18:41:58 +010018import enum
Tim Hall79d07d22020-04-27 18:20:16 +010019from collections import namedtuple
20from configparser import ConfigParser
Diego Russoea6111a2020-04-14 18:41:58 +010021
Tim Hall79d07d22020-04-27 18:20:16 +010022import numpy as np
Diego Russoea6111a2020-04-14 18:41:58 +010023
Tim Hall1bd531d2020-11-01 20:59:36 +000024from .errors import CliOptionError
25from .errors import ConfigOptionError
Dwight Lidmana9390f72020-05-13 12:00:08 +020026from .ethos_u55_regs.ethos_u55_regs import resampling_mode
Louis Verhaard69b31762020-11-17 09:45:20 +010027from .numeric_util import full_shape
Diego Russoe8a10452020-04-21 17:39:10 +010028from .numeric_util import round_up
29from .numeric_util import round_up_divide
Tim Hall4ed38bc2020-10-20 18:54:20 +010030from .operation import Kernel
Diego Russoea6111a2020-04-14 18:41:58 +010031from .operation import NpuBlockType
Tim Hall4ed38bc2020-10-20 18:54:20 +010032from .operation import PointXYZ
Diego Russoea6111a2020-04-14 18:41:58 +010033from .supported_operators import SupportedOperators
Diego Russoe8a10452020-04-21 17:39:10 +010034from .tensor import MemArea
Patrik Gustavssoneca2e952020-05-27 09:15:11 +020035from .tensor import MemType
Diego Russoe8a10452020-04-21 17:39:10 +010036from .tensor import TensorFormat
37from .tensor import TensorPurpose
Tim Hall79d07d22020-04-27 18:20:16 +010038
Tim Hall79d07d22020-04-27 18:20:16 +010039
40class Block:
41 def __init__(self, w, h, d):
42 self.width = w
43 self.height = h
44 self.depth = d
45
46 def __eq__(self, other):
47 if self.width == other.width and self.height == other.height and self.depth == other.depth:
48 return True
49 else:
50 return False
51
52 def __repr__(self):
53 return "<Block: {0},{1},{2}>".format(self.width, self.height, self.depth)
54
55 @classmethod
56 def from_string(cls, s):
57 w, h, c = (int(v) for v in s.split("x"))
58 return cls(w, h, c)
59
Louis Verhaard69b31762020-11-17 09:45:20 +010060 @classmethod
61 def from_shape(cls, shape) -> "Block":
62 """Converts the shape to a Block"""
63 shp = full_shape(3, shape, 1)
64 # Note: index from end, as len(shp) may be > 3
65 return Block(shp[-2], shp[-3], shp[-1])
66
Tim Hall79d07d22020-04-27 18:20:16 +010067
68class Rect:
69 def __init__(self, x, y, z, x2, y2, z2):
70 self.x = x
71 self.y = y
72 self.z = z
73 self.x2 = x2
74 self.y2 = y2
75 self.z2 = z2
76
77 def start(self):
78 return PointXYZ(self.x, self.y, self.z)
79
80 def end(self):
81 return PointXYZ(self.x2, self.y2, self.z2)
82
83 def size(self):
84 return Block(self.x2 - self.x + 1, self.y2 - self.y + 1, self.z2 - self.z + 1)
85
86 def __repr__(self):
87 return "<Rect: ({0},{1},{2}) ({3},{4},{5})>".format(self.x, self.y, self.z, self.x2, self.y2, self.z2)
88
89
Tim Hall79d07d22020-04-27 18:20:16 +010090class SHRAMElements:
91 IFM8 = 0
92 IFM16 = 1
93 IFM8_Elementwise = 2
94 IFM16_Elementwise = 3
Fredrik Svedberg597fd3f2020-08-13 10:02:53 +020095 IFM32 = 4
Fredrik Svedberga0c36242020-06-03 15:43:31 +020096 Acc16 = 5
97 Acc32 = 6
98 Acc40 = 7
Tim Hall79d07d22020-04-27 18:20:16 +010099 Last = Acc40
Fredrik Svedberga0c36242020-06-03 15:43:31 +0200100 BitSizes = np.array([8, 16, 8, 16, 32, 16, 32, 40], np.int32)
Louis Verhaardf98c6742020-05-12 14:22:38 +0200101 ByteSizes = BitSizes // 8
Fredrik Svedberga0c36242020-06-03 15:43:31 +0200102 PostAlign = np.array([8, 8, 8, 8, 8, 1, 1, 1], np.int32)
103 PreAlign = np.array([1, 1, 1, 1, 1, 8, 8, 8], np.int32)
Tim Hall79d07d22020-04-27 18:20:16 +0100104
105
106class SHRAMBlockConfig:
107 def __init__(self, sizes, banks):
108 assert len(banks) == SHRAMElements.Last + 1
109 self.sizes = sizes
110 self.banks = banks
111
112
Tim Hallc8a73862020-10-27 12:43:14 +0000113# Area indices must match Ethos-U SHRAM layout spec
Tim Hall79d07d22020-04-27 18:20:16 +0100114class SharedBufferArea(enum.IntEnum):
115 OFM = 0
116 Weights = 1
117 IFM = 2
118 Accumulators = 3
119 Size = Accumulators + 1
120
121
Manupa Karunaratned83d2e12020-07-20 12:05:32 +0100122class Accelerator(enum.Enum):
123 Ethos_U55_32 = "ethos-u55-32"
124 Ethos_U55_64 = "ethos-u55-64"
125 Ethos_U55_128 = "ethos-u55-128"
126 Ethos_U55_256 = "ethos-u55-256"
Tim Hallc8a73862020-10-27 12:43:14 +0000127 Ethos_U65_256 = "ethos-u65-256"
128 Ethos_U65_512 = "ethos-u65-512"
Manupa Karunaratned83d2e12020-07-20 12:05:32 +0100129
130 @classmethod
131 def member_list(cls):
132 return [e.value for e in cls]
133
134
Tim Hall1bd531d2020-11-01 20:59:36 +0000135@enum.unique
136class MemPort(enum.Enum):
137 Axi0 = enum.auto()
138 Axi1 = enum.auto()
139
140
Tim Hall79d07d22020-04-27 18:20:16 +0100141class ArchitectureFeatures:
Tim Hallc8a73862020-10-27 12:43:14 +0000142 """This class is a container for various parameters of the Ethos-U core
Diqing Zhonge8887a32020-09-24 09:53:48 +0200143 and system configuration that can be tuned, either by command line
Tim Hallc8a73862020-10-27 12:43:14 +0000144 parameters or by the Ethos-U architects. The class is often passed
Diqing Zhonge8887a32020-09-24 09:53:48 +0200145 around to passes that need to do architecture-dependent actions.
Tim Hall79d07d22020-04-27 18:20:16 +0100146
Diqing Zhonge8887a32020-09-24 09:53:48 +0200147 Note the difference between ArchitectureFeatures and CompilerOptions
Tim Hallc8a73862020-10-27 12:43:14 +0000148 - ArchitectureFeatures is for changing the Ethos-U and system architecture
Diqing Zhonge8887a32020-09-24 09:53:48 +0200149 - CompilerOptions is for changing the behaviour of the compiler
150 """
Tim Hall79d07d22020-04-27 18:20:16 +0100151
152 ArchitectureConfig = namedtuple(
153 "ArchitectureConfig", "macs cores ofm_ublock ifm_ublock shram_banks shram_granules elem_units"
154 )
155 accelerator_configs = {
Tim Hallc8a73862020-10-27 12:43:14 +0000156 Accelerator.Ethos_U65_512: ArchitectureConfig(
Fredrik Svedberga0c36242020-06-03 15:43:31 +0200157 256, 2, Block(2, 2, 8), Block(2, 2, 8), 48, [8, 8, 8, 8, 16, 8, 16, 20], 8
Manupa Karunaratned83d2e12020-07-20 12:05:32 +0100158 ),
Tim Hallc8a73862020-10-27 12:43:14 +0000159 Accelerator.Ethos_U65_256: ArchitectureConfig(
Fredrik Svedberga0c36242020-06-03 15:43:31 +0200160 256, 1, Block(2, 2, 8), Block(2, 2, 8), 48, [8, 8, 8, 8, 16, 8, 16, 20], 8
Manupa Karunaratned83d2e12020-07-20 12:05:32 +0100161 ),
162 Accelerator.Ethos_U55_256: ArchitectureConfig(
Fredrik Svedberga0c36242020-06-03 15:43:31 +0200163 256, 1, Block(2, 2, 8), Block(2, 2, 8), 48, [8, 8, 8, 8, 16, 8, 16, 20], 8
Manupa Karunaratned83d2e12020-07-20 12:05:32 +0100164 ),
165 Accelerator.Ethos_U55_128: ArchitectureConfig(
Fredrik Svedberga0c36242020-06-03 15:43:31 +0200166 128, 1, Block(2, 1, 8), Block(2, 2, 8), 24, [4, 4, 4, 4, 8, 4, 8, 12], 4
Manupa Karunaratned83d2e12020-07-20 12:05:32 +0100167 ),
168 Accelerator.Ethos_U55_64: ArchitectureConfig(
Fredrik Svedberga0c36242020-06-03 15:43:31 +0200169 64, 1, Block(1, 1, 8), Block(1, 1, 8), 16, [2, 2, 2, 2, 4, 4, 4, 8], 2
Manupa Karunaratned83d2e12020-07-20 12:05:32 +0100170 ),
171 Accelerator.Ethos_U55_32: ArchitectureConfig(
Fredrik Svedberga0c36242020-06-03 15:43:31 +0200172 32, 1, Block(1, 1, 4), Block(1, 1, 8), 16, [2, 2, 2, 2, 4, 4, 4, 4], 1
Manupa Karunaratned83d2e12020-07-20 12:05:32 +0100173 ),
Tim Hall79d07d22020-04-27 18:20:16 +0100174 }
175
176 OFMSplitDepth = 16
Manupa Karunaratned83d2e12020-07-20 12:05:32 +0100177 SubKernelMax = Block(8, 8, 65536)
Tim Hall79d07d22020-04-27 18:20:16 +0100178
Tim Hall1bd531d2020-11-01 20:59:36 +0000179 DEFAULT_CONFIG = "internal-default"
180
Tim Hall79d07d22020-04-27 18:20:16 +0100181 def __init__(
182 self,
Tim Hall1bd531d2020-11-01 20:59:36 +0000183 vela_config_files,
Tim Hall79d07d22020-04-27 18:20:16 +0100184 accelerator_config,
185 system_config,
Tim Hall1bd531d2020-11-01 20:59:36 +0000186 memory_mode,
Tim Hall79d07d22020-04-27 18:20:16 +0100187 override_block_config,
188 block_config_limit,
Tim Hall79d07d22020-04-27 18:20:16 +0100189 max_blockdep,
Patrik Gustavsson90831bc2020-08-24 16:26:11 +0200190 weight_estimation_scaling,
Tim Hall1bd531d2020-11-01 20:59:36 +0000191 verbose_config,
Tim Hall79d07d22020-04-27 18:20:16 +0100192 ):
193 accelerator_config = accelerator_config.lower()
Manupa Karunaratned83d2e12020-07-20 12:05:32 +0100194 if accelerator_config not in Accelerator.member_list():
Tim Hall1bd531d2020-11-01 20:59:36 +0000195 raise CliOptionError("--accelerator-config", self.accelerator_config, "Unknown accelerator configuration")
Manupa Karunaratned83d2e12020-07-20 12:05:32 +0100196 self.accelerator_config = Accelerator(accelerator_config)
Tim Hall79d07d22020-04-27 18:20:16 +0100197 accel_config = ArchitectureFeatures.accelerator_configs[self.accelerator_config]
198 self.config = accel_config
199
200 self.system_config = system_config
Tim Hall1bd531d2020-11-01 20:59:36 +0000201 self.memory_mode = memory_mode
Tim Hallc8a73862020-10-27 12:43:14 +0000202 self.is_ethos_u65_system = self.accelerator_config in (Accelerator.Ethos_U65_256, Accelerator.Ethos_U65_512)
Tim Hall79d07d22020-04-27 18:20:16 +0100203
Tim Hallc8a73862020-10-27 12:43:14 +0000204 self.max_outstanding_dma = 2 if self.is_ethos_u65_system else 1
Tim Hall289a41d2020-08-04 21:40:14 +0100205 self.max_outstanding_kernels = 3
206
Tim Hall79d07d22020-04-27 18:20:16 +0100207 self.ncores = accel_config.cores
208 self.ofm_ublock = accel_config.ofm_ublock
209 self.ifm_ublock = accel_config.ifm_ublock
Tim Hall79d07d22020-04-27 18:20:16 +0100210 self.ofm_block_max = Block(64, 32, 128)
211 self.override_block_config = override_block_config
212 self.block_config_limit = block_config_limit
213
Tim Hall79d07d22020-04-27 18:20:16 +0100214 self.max_blockdep = max_blockdep
Patrik Gustavsson90831bc2020-08-24 16:26:11 +0200215 self.weight_estimation_scaling = weight_estimation_scaling
Tim Hall79d07d22020-04-27 18:20:16 +0100216
217 dpu_min_height = accel_config.ofm_ublock.height
218 dpu_min_width = accel_config.ofm_ublock.width
219 dpu_dot_product_width = 8
220 dpu_min_ofm_channels = accel_config.ofm_ublock.depth
221
222 self.num_elem_wise_units = accel_config.elem_units
223 self.num_macs_per_cycle = dpu_min_height * dpu_min_width * dpu_dot_product_width * dpu_min_ofm_channels
224
Tim Hall1bd531d2020-11-01 20:59:36 +0000225 # Get system configuration and memory mode
226 self._get_vela_config(vela_config_files, verbose_config)
Tim Hall79d07d22020-04-27 18:20:16 +0100227
Tim Hall1bd531d2020-11-01 20:59:36 +0000228 self.axi_port_width = 128 if self.is_ethos_u65_system else 64
229 self.memory_bandwidths_per_cycle = self.axi_port_width * self.memory_clock_scales / 8
Tim Hall79d07d22020-04-27 18:20:16 +0100230
Tim Hall1bd531d2020-11-01 20:59:36 +0000231 self.memory_bandwidths_per_second = self.memory_bandwidths_per_cycle * self.core_clock
Tim Hall79d07d22020-04-27 18:20:16 +0100232
Diqing Zhonge8887a32020-09-24 09:53:48 +0200233 # Get output/activation performance numbers
234 self._generate_output_perf_tables(self.accelerator_config)
235
Tim Hall79d07d22020-04-27 18:20:16 +0100236 # sizes as N x H x W x C. we need to round up to these when allocating storage
237 self.storage_rounding_quantums = {
238 TensorFormat.Unknown: (1, 1, 1, 1),
239 TensorFormat.WeightsCompressed: (1, 1, 1, 1),
240 TensorFormat.NHWC: (1, 1, 1, 1),
241 TensorFormat.NHCWB16: (1, 1, 1, 16),
242 }
243
244 # brick sizes as N x H x W x C. We have to fetch whole bricks at a time
245 self.brick_sizes = {
246 TensorFormat.Unknown: (1, 1, 1, 1),
247 TensorFormat.WeightsCompressed: (1, 1, 1, 1),
248 TensorFormat.NHWC: (1, 1, 1, 1),
249 TensorFormat.NHCWB16: (1, 1, 1, 16),
250 }
251
Tim Hall79d07d22020-04-27 18:20:16 +0100252 self.default_weight_format = TensorFormat.WeightsCompressed
253 self.default_feature_map_format = TensorFormat.NHWC
254
Tim Hall79d07d22020-04-27 18:20:16 +0100255 self.tensor_storage_mem_area = {
256 # permanent mem_area
Tim Hall465582c2020-05-26 09:33:14 +0100257 TensorPurpose.Unknown: MemArea.Unknown,
Tim Hall79d07d22020-04-27 18:20:16 +0100258 TensorPurpose.Weights: self.permanent_storage_mem_area,
259 TensorPurpose.FeatureMap: self.feature_map_storage_mem_area,
Fredrik Svedberga0c36242020-06-03 15:43:31 +0200260 TensorPurpose.LUT: self.permanent_storage_mem_area,
Tim Hall79d07d22020-04-27 18:20:16 +0100261 }
262
Patrik Gustavssoneca2e952020-05-27 09:15:11 +0200263 self.tensor_storage_mem_type = {
Dwight Lidman1a9d20e2020-08-11 12:10:36 +0200264 TensorPurpose.Unknown: MemType.Unknown,
Patrik Gustavssoneca2e952020-05-27 09:15:11 +0200265 TensorPurpose.Weights: MemType.Permanent_NPU,
266 TensorPurpose.FeatureMap: MemType.Scratch,
Fredrik Svedberga0c36242020-06-03 15:43:31 +0200267 TensorPurpose.LUT: MemType.Scratch,
Patrik Gustavssoneca2e952020-05-27 09:15:11 +0200268 }
Tim Hall79d07d22020-04-27 18:20:16 +0100269
270 self.min_block_sizes = {
271 NpuBlockType.Default: (dpu_min_height, dpu_min_width),
272 NpuBlockType.VectorProduct: (1, 1),
273 NpuBlockType.ConvolutionMxN: (dpu_min_height, dpu_min_width),
274 NpuBlockType.Pooling: (dpu_min_height, dpu_min_width),
275 NpuBlockType.ConvolutionDepthWise: (dpu_min_height, dpu_min_width),
276 NpuBlockType.ElementWise: (1, 1),
Fredrik Svedberga0c36242020-06-03 15:43:31 +0200277 NpuBlockType.ReduceSum: (dpu_min_height, dpu_min_width),
Tim Hall79d07d22020-04-27 18:20:16 +0100278 }
279
280 self.sub_kernel_limits = {
281 NpuBlockType.Default: (8, 8),
282 NpuBlockType.VectorProduct: (1, 1),
283 NpuBlockType.ConvolutionMxN: (8, 8),
284 NpuBlockType.Pooling: (8, 8),
285 NpuBlockType.ConvolutionDepthWise: (8, 8),
286 NpuBlockType.ElementWise: (1, 1),
Fredrik Svedberga0c36242020-06-03 15:43:31 +0200287 NpuBlockType.ReduceSum: (8, 8),
Tim Hall79d07d22020-04-27 18:20:16 +0100288 }
289
290 # weights for scheduler search
291 from .npu_performance import make_bandwidth_array
292
293 self.bandwidth_weights = make_bandwidth_array()
294 self.bandwidth_weights[MemArea.Sram] = 1.0
295 self.bandwidth_weights[MemArea.Dram] = 10.0
296 self.bandwidth_weights[MemArea.OnChipFlash] = 2.0
297 self.bandwidth_weights[MemArea.OffChipFlash] = 20.0
298 self.cycles_weight = 40
299 self.max_sram_used_weight = 1000
300
Tim Hall1bd531d2020-11-01 20:59:36 +0000301 if self.is_spilling_enabled():
Patrik Gustavsson3ab94522020-06-29 17:36:55 +0200302 self.max_sram_used_weight = 0
Tim Hall79d07d22020-04-27 18:20:16 +0100303
304 # Shared Buffer Block allocations
305 self.shram_bank_size = 1024 # bytes
306 self.shram_size_bytes = accel_config.shram_banks * self.shram_bank_size
307 self.shram_reserved_output_banks = 2
308 self.shram_reserved_weight_banks = 0
309 self.shram_reserved_unused_banks = 2 if accel_config.shram_banks > 16 else 0
310 self.shram_total_banks = accel_config.shram_banks - self.shram_reserved_unused_banks
311 self.shram_bank_granules = np.array(accel_config.shram_granules, np.int32)
Louis Verhaard0b8268a2020-08-05 16:11:29 +0200312 self.shram_lut_size = 2048
313 # SHRAM base address of the activation lookup table
314 self.shram_lut_address = self.shram_bank_size * self.available_shram_banks(True)
Tim Hall79d07d22020-04-27 18:20:16 +0100315
316 # Build a map of acceptable IFM/OFM block configurations up to the maximum
317 # IFM/OFM block size.
318 ifm_block_max = self.get_ifm_block_size(32, self.ofm_block_max, Kernel(8, 8))
319 self.block_config_map = dict()
320 self.generate_block_config_map(Block(ifm_block_max.width, ifm_block_max.height, 128))
321
322 # Setup supported operators and restriction checkers class
Fredrik Svedberg880e7352020-08-25 11:31:47 +0200323 self.supported_operators = SupportedOperators()
Tim Hall79d07d22020-04-27 18:20:16 +0100324
Louis Verhaard0b8268a2020-08-05 16:11:29 +0200325 # Returns available number of SHRAM banks depending on activation lookup table
326 # being used or not
327 def available_shram_banks(self, uses_activation_lut):
328 banks = self.shram_total_banks
329 if uses_activation_lut and self.shram_reserved_unused_banks == 0:
330 banks -= 2
331 return banks
332
Tim Hall79d07d22020-04-27 18:20:16 +0100333 # Calculate block configuration for ALL known IFM operations and
334 # accumulator sizes. Consumers will need to select their preferred
335 # operation and bit-width at read-time.
336 def generate_block_config(self, width, height, depth):
Louis Verhaardf98c6742020-05-12 14:22:38 +0200337 # Number of bytes required for any SHRAM element for a FM of given dimensions.
338 # For IFM: size = H*W*Align(D*BYTE_WIDTH, 8)
339 # For ACC: size = H*W*Align(D,8)*BYTE_WIDTH
340 d1 = round_up(depth, SHRAMElements.PreAlign)
341 d2 = round_up(d1 * SHRAMElements.ByteSizes, SHRAMElements.PostAlign)
342 size_bytes = (height * width) * d2
343
Tim Hall79d07d22020-04-27 18:20:16 +0100344 # Convert byte size (rounded) to size in banks
345 size_banks = round_up_divide(size_bytes, self.shram_bank_size)
346 size_banks *= 2 # Double buffer the IFM/Acc (need twice as many banks)
347 # Round bank requirement to bank granularity
348 required_banks = round_up(size_banks, self.shram_bank_granules)
349 return SHRAMBlockConfig(size_bytes, required_banks)
350
351 @staticmethod
352 def make_block_config_key(width, height, depth):
353 return (int(height), int(width), int(depth))
354
355 def get_block_config(self, width, height, depth):
356 assert depth <= self.ofm_block_max.depth
357 key = ArchitectureFeatures.make_block_config_key(width, height, depth)
358 config = self.block_config_map.get(key, None)
359 return config
360
361 # Generate a key:value map of possible block configurations, where the
362 # key is compounded from the block dimensions: 0x00HHWWCC
363 def generate_block_config_map(self, block: Block):
364 for h in range(1, block.height + 1):
365 for w in range(1, block.width + 1):
366 # All possible IFM/OFM depth values
367 for c in [4, 8, 12, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96, 104, 112, 120, 128]:
368 key = ArchitectureFeatures.make_block_config_key(w, h, c)
369 self.block_config_map[key] = self.generate_block_config(w, h, c)
370
Diqing Zhonge8887a32020-09-24 09:53:48 +0200371 def _generate_output_perf_tables(self, accel_config):
372 if accel_config == Accelerator.Ethos_U55_32:
373 self.output_cycles_per_elem = (2.0, 3.0, 3.0, 3.0, 4.0, 6.0, 1.0, 2.0)
374 self.activation_cycles_per_elem = (1.0, 1.0, 0.0)
375 elif accel_config == Accelerator.Ethos_U55_64:
376 self.output_cycles_per_elem = (1.0, 1.5, 1.5, 1.5, 2.0, 3.0, 0.5, 1.0)
377 self.activation_cycles_per_elem = (1.0, 1.0, 0.0)
378 elif accel_config == Accelerator.Ethos_U55_128:
379 self.output_cycles_per_elem = (0.75, 1.25, 0.75, 0.75, 1.0, 1.5, 0.25, 0.5)
380 self.activation_cycles_per_elem = (1.0, 0.5, 0.0)
Tim Hallc8a73862020-10-27 12:43:14 +0000381 elif accel_config in (Accelerator.Ethos_U55_256, Accelerator.Ethos_U65_256):
Diqing Zhonge8887a32020-09-24 09:53:48 +0200382 self.output_cycles_per_elem = (0.625, 1.125, 0.5, 0.375, 0.5, 0.75, 0.125, 0.25)
383 self.activation_cycles_per_elem = (1.0, 0.25, 0.0)
384 else:
Tim Hallc8a73862020-10-27 12:43:14 +0000385 assert accel_config == Accelerator.Ethos_U65_512
Diqing Zhonge8887a32020-09-24 09:53:48 +0200386 self.output_cycles_per_elem = (0.3125, 0.5625, 0.25, 0.1875, 0.25, 0.375, 0.0625, 0.125)
387 self.activation_cycles_per_elem = (0.5, 0.125, 0.0)
388
Tim Hall79d07d22020-04-27 18:20:16 +0100389 def calc_ifm_block_depth(self, ifm_depth, ifm_bits):
Fredrik Svedberga0c36242020-06-03 15:43:31 +0200390 assert ifm_bits in (8, 16, 32)
Tim Hall79d07d22020-04-27 18:20:16 +0100391 assert ifm_depth > 0
392 ifm_depth = round_up(ifm_depth, self.ifm_ublock.depth)
Fredrik Svedberga0c36242020-06-03 15:43:31 +0200393 max_block_depth = 8 * 32 // ifm_bits
Tim Hall79d07d22020-04-27 18:20:16 +0100394 return min(max_block_depth, ifm_depth)
395
396 # Calculate the size of the IFM block given a depth, target OFM block and a kernel
Tim Hallc30f4952020-06-15 20:47:35 +0100397 def get_ifm_block_size(
398 self,
399 ifm_block_depth,
400 ofm_block: Block,
401 kernel: Kernel,
402 subkernel: Block = Block(8, 8, 65536),
403 ifm_resampling_mode=resampling_mode.NONE,
404 ):
Dwight Lidmana9390f72020-05-13 12:00:08 +0200405 upscaling = 1 if ifm_resampling_mode == resampling_mode.NONE else 2
Tim Hall79d07d22020-04-27 18:20:16 +0100406 # Height
407 ifm_odd_2x_height_enable = 0
408 dilated_kernel_height = ((kernel.height - 1) * kernel.dilation.y) + 1
409 ifm_block_height = (
410 (ofm_block.height - 1) * kernel.stride.y
411 + min(subkernel.height, dilated_kernel_height)
412 + ifm_odd_2x_height_enable
413 ) // upscaling
414
Dwight Lidman0538a772020-05-06 14:09:17 +0200415 ifm_block_height = round_up(ifm_block_height, self.ofm_ublock.height)
Tim Hall79d07d22020-04-27 18:20:16 +0100416
417 # Width
418 ifm_odd_2x_width_enable = 0
419 dilated_kernel_width = ((kernel.width - 1) * kernel.dilation.x) + 1
420 ifm_block_width = (
421 (ofm_block.width - 1) * kernel.stride.x
422 + min(subkernel.width, dilated_kernel_width)
423 + ifm_odd_2x_width_enable
424 ) // upscaling
425
Dwight Lidman0538a772020-05-06 14:09:17 +0200426 ifm_block_width = round_up(ifm_block_width, self.ofm_ublock.width)
Tim Hall79d07d22020-04-27 18:20:16 +0100427
428 return Block(ifm_block_width, ifm_block_height, ifm_block_depth)
429
430 @staticmethod
431 def intersects(start_a, end_a, start_b, end_b):
432 start_x = max(start_a[0], start_b[0])
433 end_x = min(end_a[0], end_b[0])
434 start_y = max(start_a[1], start_b[1])
435 end_y = min(end_a[1], end_b[1])
436 start_z = max(start_a[2], start_b[2])
437 end_z = min(end_a[2], end_b[2])
438 return ((end_x - start_x) > 0) and ((end_y - start_y) > 0) and ((end_z - start_z) > 0)
439
440 # Block job dependency:
441 # Does the VOLUME of IFMs for block job B(0) overlap with VOLUME of OFMs block jobs A(8,9,10)
442 #
443 # A | B
444 # ----------------------+------------------
445 # .... 3,4,5,6,7,8,9,10 | 0,1,2,3,4,5,6,8 10 < JOB NUMBER
446 # |<------->| dependency offset
447 #
448 MAX_BLOCKDEP = 3
449
450 # Get the coordinates of a block offset from either the end (negative)
451 # or the start (zero or positive) of the given 3d area
452 def get_offset_block_coords(self, area: Rect, block: Block, offset):
453 size = area.size()
454 # Dimensions of the region, in blocks
455 width_blocks = round_up_divide(size.width, block.width)
456 height_blocks = round_up_divide(size.height, block.height)
457 depth_blocks = round_up_divide(size.depth, block.depth)
458 total_blocks = width_blocks * height_blocks * depth_blocks
459 if offset < 0:
460 index = total_blocks + offset
461 else:
462 index = offset
463
464 if index >= total_blocks:
465 return None
466
467 # Coordinates of the indexed block
468 coord_z = block.depth * (index % depth_blocks)
469 coord_y = block.height * (index // (depth_blocks * width_blocks))
470 coord_x = block.width * ((index // depth_blocks) % width_blocks)
471
472 return (coord_x + area.x, coord_y + area.y, coord_z + area.z)
473
474 def get_first_job_input_volume(
475 self, ifm: Rect, ofm: Rect, ifm_block_depth, ofm_block: Block, kernel: Kernel, padLT, block_offset
476 ):
477 # Get ifm block size (jobs are invisibly decomposed into subkernels)
478 ifm_block = self.get_ifm_block_size(ifm_block_depth, ofm_block, kernel, self.ofm_block_max)
479 ifm_depth_blocks = round_up_divide(ifm.size().depth, ifm_block_depth)
480
481 # Which OFM block are we calculating
482 ofm_coord = self.get_offset_block_coords(ofm, ofm_block, block_offset // ifm_depth_blocks)
483 if ofm_coord is None:
484 return None
485
486 # Coordinate of the source IFM block
487 ifm_coord_x = max(0, ofm_coord[0] * kernel.stride.x - padLT[0])
488 ifm_coord_y = max(0, ofm_coord[1] * kernel.stride.y - padLT[1])
489 ifm_coord_z = ifm.z + (block_offset % ifm_depth_blocks) * ifm_block.depth
490
491 # IFM block that will be sampled for the FIRST+block_offset job in the next operator's OFM
492 start_coord = (ifm_coord_x, ifm_coord_y, ifm_coord_z)
493 end_coord = (
494 start_coord[0] + ifm_block.width,
495 start_coord[1] + ifm_block.height,
496 start_coord[2] + ifm_block.depth,
497 )
498
499 return (start_coord, end_coord, 1) # start, end, total jobs
500
501 def get_prev_job_output_volume(
Louis Verhaarde8a5a782020-11-02 18:04:27 +0100502 self, ifm: Rect, ofm: Rect, ifm_block_depth, ofm_block: Block, kernel: Kernel, block_offset
Tim Hall79d07d22020-04-27 18:20:16 +0100503 ):
504 assert block_offset >= 0
505
506 # Get OFM block's volume coordinates
507 start_coord = self.get_offset_block_coords(ofm, ofm_block, -1 - block_offset)
508 if start_coord is None:
509 return None
510 end_coord = (
511 start_coord[0] + ofm_block.width,
512 start_coord[1] + ofm_block.height,
513 start_coord[2] + ofm_block.depth,
514 )
515
516 # Calculate how many IFM blocks this OFM block requires (i.e how many jobs)
Tim Hall79d07d22020-04-27 18:20:16 +0100517 ifm_depth_blocks = round_up_divide(ifm.size().depth, ifm_block_depth)
518 ifm_depth_blocks = 1 # Overwrite with 1 to force OFM block dependency, not IFM
519
520 return (start_coord, end_coord, ifm_depth_blocks) # start, end, total jobs for this OFM block
521
522 def calc_block_dep(
523 self,
Louis Verhaarde8a5a782020-11-02 18:04:27 +0100524 prev_ifm: Rect,
525 prev_ofm: Rect,
Tim Hall79d07d22020-04-27 18:20:16 +0100526 prev_ifm_block_depth,
527 prev_ofm_block: Block,
528 prev_kernel: Kernel,
Louis Verhaarde8a5a782020-11-02 18:04:27 +0100529 ifm: Rect,
530 ofm: Rect,
Tim Hall79d07d22020-04-27 18:20:16 +0100531 ifm_block_depth,
532 ofm_block: Block,
533 kernel: Kernel,
534 padLT,
535 ):
536
537 blockdep = ArchitectureFeatures.MAX_BLOCKDEP
538
539 # Iterate over the next BLOCKDEP inputs, checking to see if a sliding window
540 # of IFM area overlaps with any previous OFM block generation.
541 elapsed_jobs = 0
Tim Hall79d07d22020-04-27 18:20:16 +0100542 for forward_offset in range(ArchitectureFeatures.MAX_BLOCKDEP):
543 # This is the IFM block we want to sample from
544 in_area = self.get_first_job_input_volume(
545 ifm, ofm, ifm_block_depth, ofm_block, kernel, padLT, forward_offset
546 )
547 if in_area is None:
548 break
549
550 # Try several previous-OFM blocks in the past (they still might comprise multiple IFM jobs)
551 outstanding_jobs = 0
552 for block_offset in range(ArchitectureFeatures.MAX_BLOCKDEP):
553 # This is the OFM block being generated by the previous op
554 out_area = self.get_prev_job_output_volume(
555 prev_ifm, prev_ofm, prev_ifm_block_depth, prev_ofm_block, prev_kernel, block_offset
556 )
557 if out_area is None:
558 break
559
560 # Block dependency is the max number of allowed outstanding jobs
561 # in the pipeline. Selected by determining how many jobs occur
562 # in between two operators' overlapping OFM->IFM block volumes
563 if ArchitectureFeatures.intersects(in_area[0], in_area[1], out_area[0], out_area[1]):
564 break
565 # Early exit if no intersections and we've seen enough jobs in the pipeline
566 elif outstanding_jobs > ArchitectureFeatures.MAX_BLOCKDEP:
567 break
568
569 # This OFM had this many jobs (accumulate over multiple OFM blocks)
570 outstanding_jobs += out_area[2]
571
572 blockdep = min(blockdep, elapsed_jobs + outstanding_jobs)
573 elapsed_jobs += in_area[2]
574 # Early exit if no intersections and we've seen enough jobs in the pipeline
575 if elapsed_jobs > ArchitectureFeatures.MAX_BLOCKDEP:
576 break
577
578 return blockdep
579
Tim Hall1bd531d2020-11-01 20:59:36 +0000580 def is_spilling_enabled(self):
Tim Hall79d07d22020-04-27 18:20:16 +0100581 """
Tim Hall1bd531d2020-11-01 20:59:36 +0000582 Spilling is a feature that allows the Ethos-U to use a dedicated SRAM as a cache for various types of data
Tim Hall79d07d22020-04-27 18:20:16 +0100583 """
Tim Hall1bd531d2020-11-01 20:59:36 +0000584 return (
585 self._mem_port_mapping(self.cache_mem_area) == MemArea.Sram and self.cache_mem_area != self.arena_mem_area
586 )
Tim Hall79d07d22020-04-27 18:20:16 +0100587
Tim Hall1bd531d2020-11-01 20:59:36 +0000588 def _mem_port_mapping(self, mem_port):
589 mem_port_mapping = {MemPort.Axi0: self.axi0_port, MemPort.Axi1: self.axi1_port}
590 return mem_port_mapping[mem_port]
Tim Hall79d07d22020-04-27 18:20:16 +0100591
Tim Hall1bd531d2020-11-01 20:59:36 +0000592 def _set_default_sys_config(self):
593 print(f"Warning: Using {ArchitectureFeatures.DEFAULT_CONFIG} values for system configuration")
594 # ArchitectureFeatures.DEFAULT_CONFIG values
595 if self.is_ethos_u65_system:
596 # Default Ethos-U65 system configuration
597 # Ethos-U65 Client-Server: SRAM (16 GB/s) and DRAM (12 GB/s)
598 self.core_clock = 1e9
599 self.axi0_port = MemArea.Sram
600 self.axi1_port = MemArea.Dram
601 self.memory_clock_scales[MemArea.Sram] = 1.0
602 self.memory_clock_scales[MemArea.Dram] = 0.75 # 3 / 4
Tim Hall79d07d22020-04-27 18:20:16 +0100603 else:
Tim Hall1bd531d2020-11-01 20:59:36 +0000604 # Default Ethos-U55 system configuration
605 # Ethos-U55 High-End Embedded: SRAM (4 GB/s) and Flash (0.5 GB/s)
606 self.core_clock = 500e6
607 self.axi0_port = MemArea.Sram
608 self.axi1_port = MemArea.OffChipFlash
609 self.memory_clock_scales[MemArea.Sram] = 1.0
610 self.memory_clock_scales[MemArea.OffChipFlash] = 0.125 # 1 / 8
Tim Hall79d07d22020-04-27 18:20:16 +0100611
Tim Hall1bd531d2020-11-01 20:59:36 +0000612 def _set_default_mem_mode(self):
613 print(f"Warning: Using {ArchitectureFeatures.DEFAULT_CONFIG} values for memory mode")
614 # ArchitectureFeatures.DEFAULT_CONFIG values
615 if self.is_ethos_u65_system:
616 # Default Ethos-U65 memory mode
617 # Dedicated SRAM: SRAM is only used by the Ethos-U
618 self.const_mem_area = MemPort.Axi1
619 self.arena_mem_area = MemPort.Axi1
620 self.cache_mem_area = MemPort.Axi0
621 self.cache_sram_size = 384 * 1024
622 else:
623 # Default Ethos-U65 memory mode
624 self.const_mem_area = MemPort.Axi1
625 self.arena_mem_area = MemPort.Axi0
626 self.cache_mem_area = MemPort.Axi0
Tim Hall79d07d22020-04-27 18:20:16 +0100627
Tim Hall1bd531d2020-11-01 20:59:36 +0000628 def _get_vela_config(self, vela_config_files, verbose_config):
629 """
630 Gets the system configuration and memory modes from one or more Vela configuration file(s) or uses some
631 defaults.
632 """
Tim Hall79d07d22020-04-27 18:20:16 +0100633
Tim Hall1bd531d2020-11-01 20:59:36 +0000634 # all properties are optional and are initialised to a value of 1 (or the equivalent)
635 self.core_clock = 1
636 self.axi0_port = MemArea(1)
637 self.axi1_port = MemArea(1)
638 self.memory_clock_scales = np.ones(MemArea.Size)
639 self.const_mem_area = MemPort(1)
640 self.arena_mem_area = MemPort(1)
641 self.cache_mem_area = MemPort(1)
642 self.cache_sram_size = 1
Tim Hall79d07d22020-04-27 18:20:16 +0100643
Tim Hall1bd531d2020-11-01 20:59:36 +0000644 # read configuration file(s)
645 self.vela_config = None
646
647 if vela_config_files is not None:
648 self.vela_config = ConfigParser()
649 self.vela_config.read(vela_config_files)
650
651 # read system configuration
652 sys_cfg_section = "System_Config." + self.system_config
653
654 if self.vela_config is not None and self.vela_config.has_section(sys_cfg_section):
655 self.core_clock = float(self._read_config(sys_cfg_section, "core_clock", self.core_clock))
656 self.axi0_port = MemArea[self._read_config(sys_cfg_section, "axi0_port", self.axi0_port)]
657 self.axi1_port = MemArea[self._read_config(sys_cfg_section, "axi1_port", self.axi1_port)]
658
659 for mem_area in (self.axi0_port, self.axi1_port):
660 self.memory_clock_scales[mem_area] = float(
661 self._read_config(
662 sys_cfg_section, mem_area.name + "_clock_scale", self.memory_clock_scales[mem_area]
663 )
664 )
665
666 elif self.system_config == ArchitectureFeatures.DEFAULT_CONFIG:
667 self._set_default_sys_config()
668
669 elif vela_config_files is None:
670 raise CliOptionError("--config", vela_config_files, "CLI Option not specified")
671
672 else:
673 raise CliOptionError(
674 "--system-config",
675 self.system_config,
676 "Section {} not found in Vela config file".format(sys_cfg_section),
Tim Hall79d07d22020-04-27 18:20:16 +0100677 )
Tim Hall79d07d22020-04-27 18:20:16 +0100678
Tim Hall1bd531d2020-11-01 20:59:36 +0000679 # read the memory mode
680 mem_mode_section = "Memory_Mode." + self.memory_mode
Tim Hall79d07d22020-04-27 18:20:16 +0100681
Tim Hall1bd531d2020-11-01 20:59:36 +0000682 if self.vela_config is not None and self.vela_config.has_section(mem_mode_section):
683 self.const_mem_area = MemPort[
684 self._read_config(mem_mode_section, "const_mem_area", self.const_mem_area.name)
685 ]
686 self.arena_mem_area = MemPort[
687 self._read_config(mem_mode_section, "arena_mem_area", self.arena_mem_area.name)
688 ]
689 self.cache_mem_area = MemPort[
690 self._read_config(mem_mode_section, "cache_mem_area", self.cache_mem_area.name)
691 ]
692 self.cache_sram_size = int(self._read_config(mem_mode_section, "cache_sram_size", self.cache_sram_size))
Patrik Gustavssoneca2e952020-05-27 09:15:11 +0200693
Tim Hall1bd531d2020-11-01 20:59:36 +0000694 elif self.memory_mode == ArchitectureFeatures.DEFAULT_CONFIG:
695 self._set_default_mem_mode()
Patrik Gustavsson5f47c052020-06-25 12:56:04 +0200696
Tim Hall1bd531d2020-11-01 20:59:36 +0000697 elif vela_config_files is None:
698 raise CliOptionError("--config", vela_config_files, "CLI Option not specified")
Patrik Gustavssoneca2e952020-05-27 09:15:11 +0200699
Tim Hall1bd531d2020-11-01 20:59:36 +0000700 else:
701 raise CliOptionError(
702 "--memory-mode", self.memory_mode, "Section {} not found in Vela config file".format(mem_mode_section),
703 )
Tim Hall79d07d22020-04-27 18:20:16 +0100704
Tim Hall1bd531d2020-11-01 20:59:36 +0000705 # override sram to onchipflash
706 if self._mem_port_mapping(self.const_mem_area) == MemArea.Sram:
707 if self.const_mem_area == self.arena_mem_area == self.cache_mem_area:
708 print(
709 "Info: Changing const_mem_area from Sram to OnChipFlash. This will use the same characteristics as"
710 " Sram."
711 )
712 if self.const_mem_area == MemPort.Axi0:
713 self.const_mem_area = MemPort.Axi1
714 self.axi1_port = MemArea.OnChipFlash
715 else:
716 self.const_mem_area = MemPort.Axi0
717 self.axi0_port = MemArea.OnChipFlash
718 self.memory_clock_scales[MemArea.OnChipFlash] = self.memory_clock_scales[MemArea.Sram]
719
720 # check configuration
721 if self._mem_port_mapping(self.cache_mem_area) != MemArea.Sram:
722 raise ConfigOptionError("cache_mem_area", self._mem_port_mapping(self.cache_mem_area).name, "Sram")
723
724 if self.is_ethos_u65_system:
725 if self._mem_port_mapping(self.const_mem_area) not in (
726 MemArea.Dram,
727 MemArea.OnChipFlash,
728 MemArea.OffChipFlash,
729 ):
730 raise ConfigOptionError(
731 "const_mem_area",
732 self._mem_port_mapping(self.const_mem_area).name,
733 "Dram or OnChipFlash or OffChipFlash",
734 )
735
736 if self._mem_port_mapping(self.arena_mem_area) not in (MemArea.Sram, MemArea.Dram):
737 raise ConfigOptionError(
738 "arena_mem_area", self._mem_port_mapping(self.arena_mem_area).name, "Sram or Dram"
739 )
740 else:
741 if self._mem_port_mapping(self.const_mem_area) not in (MemArea.OnChipFlash, MemArea.OffChipFlash):
742 raise ConfigOptionError(
743 "const_mem_area", self._mem_port_mapping(self.const_mem_area).name, "OnChipFlash or OffChipFlash"
744 )
745
746 if self._mem_port_mapping(self.arena_mem_area) != MemArea.Sram:
747 raise ConfigOptionError("arena_mem_area", self._mem_port_mapping(self.arena_mem_area).name, "Sram")
748
749 # assign existing memory areas
750 self.permanent_storage_mem_area = self._mem_port_mapping(self.const_mem_area)
751 self.feature_map_storage_mem_area = self._mem_port_mapping(self.arena_mem_area)
752 self.fast_storage_mem_area = self._mem_port_mapping(self.cache_mem_area)
753
754 self.sram_size = self.cache_sram_size if self.is_spilling_enabled() else 9999 * 1024 * 1024
755
756 # display the system configuration and memory mode
757 if verbose_config:
758 print(f"System Configuration ({self.system_config}):")
759 print(f" core_clock = {self.core_clock}")
760 print(f" axi0_port = {self.axi0_port.name}")
761 print(f" axi1_port = {self.axi1_port.name}")
762 for mem in (MemArea.Sram, MemArea.Dram, MemArea.OnChipFlash, MemArea.OffChipFlash):
763 print(f" {mem.name}_clock_scales = {self.memory_clock_scales[mem]}")
764
765 print(f"Memory Mode ({self.memory_mode}):")
766 print(f" const_mem_area = {self.const_mem_area.name}")
767 print(f" arena_mem_area = {self.arena_mem_area.name}")
768 print(f" cache_mem_area = {self.cache_mem_area.name}")
769 print(f" cache_sram_size = {self.cache_sram_size}")
770
771 print("Architecture Settings:")
772 print(f" permanent_storage_mem_area = {self.permanent_storage_mem_area.name}")
773 print(f" feature_map_storage_mem_area = {self.feature_map_storage_mem_area.name}")
774 print(f" fast_storage_mem_area = {self.fast_storage_mem_area.name}")
775 print(f" sram_size = {self.sram_size}")
776
777 def _read_config(self, section, key, current_value):
Tim Hall79d07d22020-04-27 18:20:16 +0100778 """
Tim Hall1bd531d2020-11-01 20:59:36 +0000779 Reads a given key from a particular section in the Vela config file. If the section contains the 'inherit'
780 option then we recurse into the section specified. If inherited sections result in multiple keys for a
781 particular option then the key from the parent section is used, regardless of the parsing order
Tim Hall79d07d22020-04-27 18:20:16 +0100782 """
Tim Hall1bd531d2020-11-01 20:59:36 +0000783 if not self.vela_config.has_section(section):
784 raise ConfigOptionError(
785 "section", "{}. The section was not found in the Vela config file(s)".format(section)
786 )
787
788 result = str(current_value)
789 if self.vela_config.has_option(section, "inherit"):
790 inheritance_section = self.vela_config.get(section, "inherit")
791 # check for recursion loop
792 if inheritance_section == section:
793 raise ConfigOptionError(
794 "inherit",
795 "{}. This references its own section and recursion is not allowed".format(inheritance_section),
796 )
797 result = self._read_config(inheritance_section, key, result)
798
799 if self.vela_config.has_option(section, key):
800 result = self.vela_config.get(section, key)
801
Tim Hall79d07d22020-04-27 18:20:16 +0100802 return result