code: def analyze_scratch_memory_acesses(
      mod: tvm.IRModule, candidate_regions_for_scratch: List[int]
  ):
      """
      This function analyzes the IRModule for intermediary tensors that can be resulting
      from a offset of pool variables (via Let nodes) and/or allocate nodes. The allocate
      nodes will be folded into a single TVMBackendallocWorkspace call with offsets. Ultimately
      this will produce a mapping from each such node to a RegionOffset named tuple that
      has the region and the obtained offset, as mentioned above.
  
      Parameters
      ----------
      mod: tvm.IRModule
          The TIR module containing ethosu extern calls
      candidate_regions_for_scratch: List[int]
          A list of region integers that could be used for scratch regions
  
      Returns
      -------
      scratch_region_map : Dict[tvm.tir.Var, RegionOffset]
          A map between buffer vars to scratch regions they are assigned
      tvm_backend_alloc_workspace_size : int
          The size of tvm_backend_alloc_workspace call required to service
          remaining allocate nodes if any
      tvm_backend_alloc_workspace_region : int
          The region associated with the tvm_backend_alloc_workspace
      """
      scratch_region_map = dict()
      pool_var_region_map = dict()
      # There should only be a single function
      assert len(mod.functions.items()) == 1
      primfunc = mod.functions.items()[0][1]
      if "pool_args" in primfunc.attrs.keys():
          pool_args = primfunc.attrs["pool_args"]
          for pool_arg in pool_args:
              pool_param = primfunc.params[int(pool_arg.pool_var_idx)]
              pool_var_region_map[
                  pool_param
              ] = candidate_regions_for_scratch.pop()
              scratch_region_map[pool_param] = RegionOffset(
                  region=pool_var_region_map[pool_param], offset=None
              )
  
      def analyze_pool_access(stmt):
          if isinstance(stmt, tvm.tir.stmt.LetStmt):
              call_address_of = stmt.value
              load = call_address_of.args[0]
              pool_var = load.buffer.data
              scratch_region_map[stmt.var] = RegionOffset(
                  region=pool_var_region_map[pool_var],
                  offset=int(load.indices[0]),
              )
  
      tvm.tir.stmt_functor.post_order_visit(
          primfunc.body, analyze_pool_access
      )
  
      dynamic_allocation_region = None
      if len(candidate_regions_for_scratch) > 0:
          dynamic_allocation_region = (
              candidate_regions_for_scratch.pop()
          )
          dynamic_allocation_size = 0
  
          # If there are tir.Allocate remaining by now, they need to be serviced via
          # dynamic_allocation calls.
          def analyze_remaining_allocates(stmt):
              nonlocal dynamic_allocation_size
              if isinstance(stmt, tvm.tir.stmt.Allocate):
                  allocate = stmt
                  pointer_type = allocate.buffer_var.type_annotation
                  storage_scope = pointer_type.storage_scope
                  if storage_scope == "global":
                      dtype_bytes = (
                          np.iinfo(np.dtype(allocate.dtype)).bits // 8
                      )
                      size_in_bytes = int(
                          dtype_bytes * np.prod(list(allocate.extents))
                      )
                      # Every memory address the NPU access have to be 16 byte aligned
                      size_in_bytes = util.round_up(size_in_bytes, 16)
                      address = dynamic_allocation_size
                      dynamic_allocation_size += size_in_bytes
                      scratch_region_map[
                          allocate.buffer_var
                      ] = RegionOffset(
                          region=dynamic_allocation_region,
                          offset=address,
                      )
  
          tvm.tir.stmt_functor.post_order_visit(
              primfunc.body, analyze_remaining_allocates
          )
  
      return (
          scratch_region_map,
          dynamic_allocation_size,
          dynamic_allocation_region,
      )
  
imports: from typing import Dict, NamedTuple, Tuple, Union, List
  from enum import auto
  from enum import Enum
  import numpy
  import ethosu.vela.api
  import tvm
  from tvm.tir import stmt_functor
  from tvm.relay.backend.contrib.ethosu import util
  from tvm.relay.backend.contrib.ethosu import vela_api
  from tvm.relay.backend.contrib.ethosu.tir import spec
  from tvm.relay.backend.contrib.ethosu.tir import utils
label_desc: Use image node_id 2 for calling a global function with example usage: analyze_scratch_memory_acesses(mod, candidate_regions_for_scratch) and returns: scratch_region_map,  dynamic_allocation_size,  dynamic_allocation_region