files: # Copyright (c) 2013 Potential Ventures Ltd
  # Copyright (c) 2013 SolarFlare Communications Inc
  # All rights reserved.
  #
  # Redistribution and use in source and binary forms, with or without
  # modification, are permitted provided that the following conditions are met:
  #     * Redistributions of source code must retain the above copyright
  #       notice, this list of conditions and the following disclaimer.
  #     * Redistributions in binary form must reproduce the above copyright
  #       notice, this list of conditions and the following disclaimer in the
  #       documentation and/or other materials provided with the distribution.
  #     * Neither the name of Potential Ventures Ltd,
  #       SolarFlare Communications Inc nor the
  #       names of its contributors may be used to endorse or promote products
  #       derived from this software without specific prior written permission.
  #
  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  # DISCLAIMED. IN NO EVENT SHALL POTENTIAL VENTURES LTD BE LIABLE FOR ANY
  # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  
  import sys
  import time
  import logging
  import functools
  import inspect
  import os
  
  import cocotb
  from cocotb.log import SimLog
  from cocotb.result import ReturnValue
  from cocotb.utils import get_sim_time, lazy_property, remove_traceback_frames, extract_coro_stack
  from cocotb import outcomes
  
  # Sadly the Python standard logging module is very slow so it's better not to
  # make any calls by testing a boolean flag first
  if "COCOTB_SCHEDULER_DEBUG" in os.environ:
      _debug = True
  else:
      _debug = False
  
  
  def public(f):
      """Use a decorator to avoid retyping function/class names.
  
      * Based on an idea by Duncan Booth:
      http://groups.google.com/group/comp.lang.python/msg/11cbb03e09611b8a
      * Improved via a suggestion by Dave Angel:
      http://groups.google.com/group/comp.lang.python/msg/3d400fb22d8a42e1
      """
      all = sys.modules[f.__module__].__dict__.setdefault('__all__', [])
      if f.__name__ not in all:  # Prevent duplicates if run from an IDE.
          all.append(f.__name__)
      return f
  
  
  public(public)  # Emulate decorating ourself
  
  
  @public
  class CoroutineComplete(Exception):
      """To ensure that a coroutine has completed before we fire any triggers
      that are blocked waiting for the coroutine to end, we create a sub-class
      exception that the scheduler catches and the callbacks are attached
      here.
      """
  
      def __init__(self, text=""):
          Exception.__init__(self, text)
  
  
  class RunningTask:
      """Per instance wrapper around a running generator.
  
      Provides the following:
  
          task.join() creates a Trigger that will fire when this coroutine
          completes.
  
          task.kill() will destroy a coroutine instance (and cause any Join
          triggers to fire.
      """
  
      _id_count = 0  # used by the scheduler for debug
  
      def __init__(self, inst):
  
          if inspect.iscoroutine(inst):
              self._natively_awaitable = True
          elif inspect.isgenerator(inst):
              self._natively_awaitable = False
          elif inspect.iscoroutinefunction(inst):
              raise TypeError(
                  "Coroutine function {} should be called prior to being "
                  "scheduled."
                  .format(inst))
          elif sys.version_info >= (3, 6) and inspect.isasyncgen(inst):
              raise TypeError(
                  "{} is an async generator, not a coroutine. "
                  "You likely used the yield keyword instead of await.".format(
                      inst.__qualname__))
          else:
              raise TypeError(
                  "%s isn't a valid coroutine! Did you forget to use the yield keyword?" % inst)
          self._coro = inst
          self._started = False
          self._callbacks = []
          self._outcome = None
          self._trigger = None
  
          self._task_id = self._id_count
          RunningTask._id_count += 1
          self.__name__ = "Task %d" % self._task_id
          self.__qualname__ = self.__name__
  
      @lazy_property
      def log(self):
          # Creating a logger is expensive, only do it if we actually plan to
          # log anything
          return SimLog("cocotb.coroutine.%s" % self.__qualname__, id(self))
  
      @property
      def retval(self):
          if self._outcome is None:
              raise RuntimeError("coroutine is not complete")
          return self._outcome.get()
  
      @property
      def _finished(self):
          return self._outcome is not None
  
      def __iter__(self):
          return self
  
      def __str__(self):
          return "<{}>".format(self.__name__)
  
      def _get_coro_stack(self):
          """Get the coroutine callstack of this Task."""
          coro_stack = extract_coro_stack(self._coro)
  
          # Remove Trigger.__await__() from the stack, as it's not really useful
          if self._natively_awaitable and len(coro_stack):
              if coro_stack[-1].name == '__await__':
                  coro_stack.pop()
  
          return coro_stack
  
      def __repr__(self):
          coro_stack = self._get_coro_stack()
  
          if cocotb.scheduler._current_task is self:
              fmt = "<{name} running coro={coro}()>"
          elif self._finished:
              fmt = "<{name} finished coro={coro}() outcome={outcome}>"
          elif self._trigger is not None:
              fmt = "<{name} pending coro={coro}() trigger={trigger}>"
          elif not self._started:
              fmt = "<{name} created coro={coro}()>"
          else:
              fmt = "<{name} adding coro={coro}()>"
  
          try:
              coro_name = coro_stack[-1].name
          # coro_stack may be empty if:
          # - exhausted generator
          # - finished coroutine
          except IndexError:
              coro_name = self._coro.__name__
  
          repr_string = fmt.format(
              name=self.__name__,
              coro=coro_name,
              trigger=self._trigger,
              outcome=self._outcome
          )
          return repr_string
  
      def _advance(self, outcome):
          """Advance to the next yield in this coroutine.
  
          Args:
              outcome: The :any:`outcomes.Outcome` object to resume with.
  
          Returns:
              The object yielded from the coroutine
  
          Raises:
              CoroutineComplete: If the coroutine returns or throws an error, self._outcome is set, and
             :exc:`CoroutineComplete` is thrown.
          """
          try:
              self._started = True
              return outcome.send(self._coro)
          except ReturnValue as e:
              self._outcome = outcomes.Value(e.retval)
              raise CoroutineComplete()
          except StopIteration as e:
              self._outcome = outcomes.Value(e.value)
              raise CoroutineComplete()
          except BaseException as e:
              self._outcome = outcomes.Error(remove_traceback_frames(e, ['_advance', 'send']))
              raise CoroutineComplete()
  
      def send(self, value):
          return self._coro.send(value)
  
      def throw(self, exc):
          return self._coro.throw(exc)
  
      def close(self):
          return self._coro.close()
  
      def kill(self):
          """Kill a coroutine."""
          if self._outcome is not None:
              # already finished, nothing to kill
              return
  
          if _debug:
              self.log.debug("kill() called on coroutine")
          # todo: probably better to throw an exception for anyone waiting on the coroutine
          self._outcome = outcomes.Value(None)
          cocotb.scheduler.unschedule(self)
  
      def join(self):
          """Return a trigger that will fire when the wrapped coroutine exits."""
          return cocotb.triggers.Join(self
  
  [... the rest is omitted ...]
files: cocotb/decorators.py
files: # Copyright (c) 2013 Potential Ventures Ltd
  # Copyright (c) 2013 SolarFlare Communications Inc
  # All rights reserved.
  #
  # Redistribution and use in source and binary forms, with or without
  # modification, are permitted provided that the following conditions are met:
  #     * Redistributions of source code must retain the above copyright
  #       notice, this list of conditions and the following disclaimer.
  #     * Redistributions in binary form must reproduce the above copyright
  #       notice, this list of conditions and the following disclaimer in the
  #       documentation and/or other materials provided with the distribution.
  #     * Neither the name of Potential Ventures Ltd,
  #       SolarFlare Communications Inc nor the
  #       names of its contributors may be used to endorse or promote products
  #       derived from this software without specific prior written permission.
  #
  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  # DISCLAIMED. IN NO EVENT SHALL POTENTIAL VENTURES LTD BE LIABLE FOR ANY
  # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  
  import sys
  import time
  import logging
  import functools
  import inspect
  import os
  
  import cocotb
  from cocotb.log import SimLog
  from cocotb.result import ReturnValue
  from cocotb.utils import get_sim_time, lazy_property, remove_traceback_frames
  from cocotb import outcomes
  
  # Sadly the Python standard logging module is very slow so it's better not to
  # make any calls by testing a boolean flag first
  if "COCOTB_SCHEDULER_DEBUG" in os.environ:
      _debug = True
  else:
      _debug = False
  
  
  def public(f):
      """Use a decorator to avoid retyping function/class names.
  
      * Based on an idea by Duncan Booth:
      http://groups.google.com/group/comp.lang.python/msg/11cbb03e09611b8a
      * Improved via a suggestion by Dave Angel:
      http://groups.google.com/group/comp.lang.python/msg/3d400fb22d8a42e1
      """
      all = sys.modules[f.__module__].__dict__.setdefault('__all__', [])
      if f.__name__ not in all:  # Prevent duplicates if run from an IDE.
          all.append(f.__name__)
      return f
  
  
  public(public)  # Emulate decorating ourself
  
  
  @public
  class CoroutineComplete(Exception):
      """To ensure that a coroutine has completed before we fire any triggers
      that are blocked waiting for the coroutine to end, we create a sub-class
      exception that the scheduler catches and the callbacks are attached
      here.
      """
  
      def __init__(self, text=""):
          Exception.__init__(self, text)
  
  
  class RunningTask:
      """Per instance wrapper around a running generator.
  
      Provides the following:
  
          task.join() creates a Trigger that will fire when this coroutine
          completes.
  
          task.kill() will destroy a coroutine instance (and cause any Join
          triggers to fire.
      """
  
      def __init__(self, inst):
  
          if inspect.iscoroutine(inst):
              self._natively_awaitable = True
          elif inspect.isgenerator(inst):
              self._natively_awaitable = False
          elif inspect.iscoroutinefunction(inst):
              raise TypeError(
                  "Coroutine function {} should be called prior to being "
                  "scheduled."
                  .format(inst))
          elif sys.version_info >= (3, 6) and inspect.isasyncgen(inst):
              raise TypeError(
                  "{} is an async generator, not a coroutine. "
                  "You likely used the yield keyword instead of await.".format(
                      inst.__qualname__))
          else:
              raise TypeError(
                  "%s isn't a valid coroutine! Did you forget to use the yield keyword?" % inst)
          self._coro = inst
          self.__name__ = inst.__name__
          self.__qualname__ = inst.__qualname__
          self._started = False
          self._callbacks = []
          self._outcome = None
          self._trigger = None
  
      @lazy_property
      def log(self):
          # Creating a logger is expensive, only do it if we actually plan to
          # log anything
          return SimLog("cocotb.coroutine.%s" % self.__qualname__, id(self))
  
      @property
      def retval(self):
          if self._outcome is None:
              raise RuntimeError("coroutine is not complete")
          return self._outcome.get()
  
      @property
      def _finished(self):
          return self._outcome is not None
  
      def __iter__(self):
          return self
  
      def __str__(self):
          return str(self.__qualname__)
  
      def _advance(self, outcome):
          """Advance to the next yield in this coroutine.
  
          Args:
              outcome: The :any:`outcomes.Outcome` object to resume with.
  
          Returns:
              The object yielded from the coroutine
  
          Raises:
              CoroutineComplete: If the coroutine returns or throws an error, self._outcome is set, and
             :exc:`CoroutineComplete` is thrown.
          """
          try:
              self._started = True
              return outcome.send(self._coro)
          except ReturnValue as e:
              self._outcome = outcomes.Value(e.retval)
              raise CoroutineComplete()
          except StopIteration as e:
              self._outcome = outcomes.Value(e.value)
              raise CoroutineComplete()
          except BaseException as e:
              self._outcome = outcomes.Error(remove_traceback_frames(e, ['_advance', 'send']))
              raise CoroutineComplete()
  
      def send(self, value):
          return self._coro.send(value)
  
      def throw(self, exc):
          return self._coro.throw(exc)
  
      def close(self):
          return self._coro.close()
  
      def kill(self):
          """Kill a coroutine."""
          if self._outcome is not None:
              # already finished, nothing to kill
              return
  
          if _debug:
              self.log.debug("kill() called on coroutine")
          # todo: probably better to throw an exception for anyone waiting on the coroutine
          self._outcome = outcomes.Value(None)
          cocotb.scheduler.unschedule(self)
  
      def join(self):
          """Return a trigger that will fire when the wrapped coroutine exits."""
          return cocotb.triggers.Join(self)
  
      def has_started(self):
          return self._started
  
      def __bool__(self):
          """Provide boolean testing
              if the coroutine has finished return false
              otherwise return true"""
          return not self._finished
  
      def __await__(self):
          # It's tempting to use `return (yield from self._coro)` here,
          # which bypasses the scheduler. Unfortunately, this means that
          # we can't keep track of the result or state of the coroutine,
          # things which we expose in our public API. If you want the
          # efficiency of bypassing the scheduler, remove the `@coroutine`
          # decorator from your `async` functions.
  
          # Hand the coroutine back to the scheduler trampoline.
          return (yield self)
  
  
  class RunningCoroutine(RunningTask):
      """
      The result of calling a :any:`cocotb.coroutine` decorated coroutine.
  
      All this class does is provide some extra attributes.
      """
  
      def __init__(self, inst, parent):
          RunningTask.__init__(self, inst)
          self._parent = parent
          self.__doc__ = parent._func.__doc__
          self.module = parent._func.__module__
          self.funcname = parent._func.__name__
  
  
  class RunningTest(RunningCoroutine):
      """Add some useful Test functionality to a RunningCoroutine."""
  
      class ErrorLogHandler(logging.Handler):
          def __init__(self, fn):
              self.fn = fn
              logging.Handler.__init__(self, level=logging.DEBUG)
  
          def handle(self, record):
              # For historical reasons, only logs sent directly to the `cocotb`
              # logger are recorded - logs to `cocotb.scheduler` for instance
              # are not recorded. Changing this
  
  [... the rest is omitted ...]
files: cocotb/decorators.py
files: # Copyright (c) 2013 Potential Ventures Ltd
  # Copyright (c) 2013 SolarFlare Communications Inc
  # All rights reserved.
  #
  # Redistribution and use in source and binary forms, with or without
  # modification, are permitted provided that the following conditions are met:
  #     * Redistributions of source code must retain the above copyright
  #       notice, this list of conditions and the following disclaimer.
  #     * Redistributions in binary form must reproduce the above copyright
  #       notice, this list of conditions and the following disclaimer in the
  #       documentation and/or other materials provided with the distribution.
  #     * Neither the name of Potential Ventures Ltd,
  #       SolarFlare Communications Inc nor the
  #       names of its contributors may be used to endorse or promote products
  #       derived from this software without specific prior written permission.
  #
  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  # DISCLAIMED. IN NO EVENT SHALL POTENTIAL VENTURES LTD BE LIABLE FOR ANY
  # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  
  """Collection of handy functions."""
  
  import ctypes
  import math
  import os
  import sys
  import traceback
  import weakref
  import functools
  import warnings
  
  from cocotb import simulator
  
  
  def _get_simulator_precision():
      # cache and replace this function
      precision = simulator.get_precision()
      global _get_simulator_precision
      _get_simulator_precision = precision.__int__
      return _get_simulator_precision()
  
  
  def get_python_integer_types():
      warnings.warn(
          "This is an internal cocotb function, use six.integer_types instead",
          DeprecationWarning)
      return (int,)
  
  
  # Simulator helper functions
  def get_sim_time(units=None):
      """Retrieves the simulation time from the simulator.
  
      Args:
          units (str or None, optional): String specifying the units of the result
              (one of ``None``, ``'fs'``, ``'ps'``, ``'ns'``, ``'us'``, ``'ms'``, ``'sec'``).
              ``None`` will return the raw simulation time.
  
      Returns:
          The simulation time in the specified units.
      """
      timeh, timel = simulator.get_sim_time()
  
      result = (timeh << 32 | timel)
  
      if units is not None:
          result = get_time_from_sim_steps(result, units)
  
      return result
  
  
  def _ldexp10(frac, exp):
      """ Like math.ldexp, but base 10 """
      # using * or / separately prevents rounding errors if `frac` is a
      # high-precision type
      if exp > 0:
          return frac * (10 ** exp)
      else:
          return frac / (10 ** -exp)
  
  
  def get_time_from_sim_steps(steps, units):
      """Calculates simulation time in the specified *units* from the *steps* based
      on the simulator precision.
  
      Args:
          steps (int): Number of simulation steps.
          units (str): String specifying the units of the result
              (one of ``'fs'``, ``'ps'``, ``'ns'``, ``'us'``, ``'ms'``, ``'sec'``).
  
      Returns:
          The simulation time in the specified units.
      """
      return _ldexp10(steps, _get_simulator_precision() - _get_log_time_scale(units))
  
  
  def get_sim_steps(time, units=None):
      """Calculates the number of simulation time steps for a given amount of *time*.
  
      Args:
          time (numbers.Real or decimal.Decimal):  The value to convert to simulation time steps.
          units (str or None, optional):  String specifying the units of the result
              (one of ``None``, ``'fs'``, ``'ps'``, ``'ns'``, ``'us'``, ``'ms'``, ``'sec'``).
              ``None`` means time is already in simulation time steps.
  
      Returns:
          int: The number of simulation time steps.
  
      Raises:
          :exc:`ValueError`: If given *time* cannot be represented by simulator precision.
      """
      result = time
      if units is not None:
          result = _ldexp10(result, _get_log_time_scale(units) - _get_simulator_precision())
  
      result_rounded = math.floor(result)
  
      if result_rounded != result:
          raise ValueError("Unable to accurately represent {0}({1}) with the "
                           "simulator precision of 1e{2}".format(
                               time, units, _get_simulator_precision()))
  
      return int(result_rounded)
  
  
  def _get_log_time_scale(units):
      """Retrieves the ``log10()`` of the scale factor for a given time unit.
  
      Args:
          units (str): String specifying the units
              (one of ``'fs'``, ``'ps'``, ``'ns'``, ``'us'``, ``'ms'``, ``'sec'``).
  
      Returns:
          The the ``log10()`` of the scale factor for the time unit.
      """
      scale = {
          'fs' :    -15,
          'ps' :    -12,
          'ns' :     -9,
          'us' :     -6,
          'ms' :     -3,
          'sec':      0}
  
      units_lwr = units.lower()
      if units_lwr not in scale:
          raise ValueError("Invalid unit ({0}) provided".format(units))
      else:
          return scale[units_lwr]
  
  # Ctypes helper functions
  
  
  def pack(ctypes_obj):
      """Convert a :mod:`ctypes` structure into a Python string.
  
      Args:
          ctypes_obj (ctypes.Structure): The :mod:`ctypes` structure to convert to a string.
  
      Returns:
          New Python string containing the bytes from memory holding *ctypes_obj*.
      """
      return ctypes.string_at(ctypes.addressof(ctypes_obj),
                              ctypes.sizeof(ctypes_obj))
  
  
  def unpack(ctypes_obj, string, bytes=None):
      """Unpack a Python string into a :mod:`ctypes` structure.
  
      If the length of *string* is not the correct size for the memory
      footprint of the :mod:`ctypes` structure then the *bytes* keyword argument
      must be used.
  
      Args:
          ctypes_obj (ctypes.Structure): The :mod:`ctypes` structure to pack into.
          string (str):  String to copy over the *ctypes_obj* memory space.
          bytes (int, optional): Number of bytes to copy.
              Defaults to ``None``, meaning the length of *string* is used.
  
      Raises:
          :exc:`ValueError`: If length of *string* and size of *ctypes_obj*
              are not equal.
          :exc:`MemoryError`: If *bytes* is longer than size of *ctypes_obj*.
      """
      if bytes is None:
          if len(string) != ctypes.sizeof(ctypes_obj):
              raise ValueError("Attempt to unpack a string of size %d into a \
                  struct of size %d" % (len(string), ctypes.sizeof(ctypes_obj)))
          bytes = len(string)
  
      if bytes > ctypes.sizeof(ctypes_obj):
          raise MemoryError("Attempt to unpack %d bytes over an object \
                          of size %d" % (bytes, ctypes.sizeof(ctypes_obj)))
  
      ctypes.memmove(ctypes.addressof(ctypes_obj), string, bytes)
  
  
  import cocotb.ANSI as ANSI
  
  
  # A note on the use of latin1 in the deprecations below:
  # Latin1 is the only encoding `e` that satisfies
  # `all(chr(x).encode(e) == bytes([x]) for x in range(255))`
  # Our use of `ord` and `chr` throughout other bits of code make this the most
  # compatible choice of encoding. Under this convention, old code can be upgraded
  # by changing "binary" strings from `"\x12\x34"` to `b"\x12\x34"`.
  
  
  def _sane(x: bytes) -> str:
      r = ""
     
  
  [... the rest is omitted ...]
files: cocotb/utils.py
files: # Copyright (c) 2013 Potential Ventures Ltd
  # Copyright (c) 2013 SolarFlare Communications Inc
  # All rights reserved.
  #
  # Redistribution and use in source and binary forms, with or without
  # modification, are permitted provided that the following conditions are met:
  #     * Redistributions of source code must retain the above copyright
  #       notice, this list of conditions and the following disclaimer.
  #     * Redistributions in binary form must reproduce the above copyright
  #       notice, this list of conditions and the following disclaimer in the
  #       documentation and/or other materials provided with the distribution.
  #     * Neither the name of Potential Ventures Ltd,
  #       SolarFlare Communications Inc nor the
  #       names of its contributors may be used to endorse or promote products
  #       derived from this software without specific prior written permission.
  #
  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  # DISCLAIMED. IN NO EVENT SHALL POTENTIAL VENTURES LTD BE LIABLE FOR ANY
  # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  
  """Collection of handy functions."""
  
  import ctypes
  import math
  import os
  import sys
  import weakref
  import functools
  import warnings
  
  from cocotb import simulator
  
  
  def _get_simulator_precision():
      # cache and replace this function
      precision = simulator.get_precision()
      global _get_simulator_precision
      _get_simulator_precision = precision.__int__
      return _get_simulator_precision()
  
  
  def get_python_integer_types():
      warnings.warn(
          "This is an internal cocotb function, use six.integer_types instead",
          DeprecationWarning)
      return (int,)
  
  
  # Simulator helper functions
  def get_sim_time(units=None):
      """Retrieves the simulation time from the simulator.
  
      Args:
          units (str or None, optional): String specifying the units of the result
              (one of ``None``, ``'fs'``, ``'ps'``, ``'ns'``, ``'us'``, ``'ms'``, ``'sec'``).
              ``None`` will return the raw simulation time.
  
      Returns:
          The simulation time in the specified units.
      """
      timeh, timel = simulator.get_sim_time()
  
      result = (timeh << 32 | timel)
  
      if units is not None:
          result = get_time_from_sim_steps(result, units)
  
      return result
  
  
  def _ldexp10(frac, exp):
      """ Like math.ldexp, but base 10 """
      # using * or / separately prevents rounding errors if `frac` is a
      # high-precision type
      if exp > 0:
          return frac * (10 ** exp)
      else:
          return frac / (10 ** -exp)
  
  
  def get_time_from_sim_steps(steps, units):
      """Calculates simulation time in the specified *units* from the *steps* based
      on the simulator precision.
  
      Args:
          steps (int): Number of simulation steps.
          units (str): String specifying the units of the result
              (one of ``'fs'``, ``'ps'``, ``'ns'``, ``'us'``, ``'ms'``, ``'sec'``).
  
      Returns:
          The simulation time in the specified units.
      """
      return _ldexp10(steps, _get_simulator_precision() - _get_log_time_scale(units))
  
  
  def get_sim_steps(time, units=None):
      """Calculates the number of simulation time steps for a given amount of *time*.
  
      Args:
          time (numbers.Real or decimal.Decimal):  The value to convert to simulation time steps.
          units (str or None, optional):  String specifying the units of the result
              (one of ``None``, ``'fs'``, ``'ps'``, ``'ns'``, ``'us'``, ``'ms'``, ``'sec'``).
              ``None`` means time is already in simulation time steps.
  
      Returns:
          int: The number of simulation time steps.
  
      Raises:
          :exc:`ValueError`: If given *time* cannot be represented by simulator precision.
      """
      result = time
      if units is not None:
          result = _ldexp10(result, _get_log_time_scale(units) - _get_simulator_precision())
  
      result_rounded = math.floor(result)
  
      if result_rounded != result:
          raise ValueError("Unable to accurately represent {0}({1}) with the "
                           "simulator precision of 1e{2}".format(
                               time, units, _get_simulator_precision()))
  
      return int(result_rounded)
  
  
  def _get_log_time_scale(units):
      """Retrieves the ``log10()`` of the scale factor for a given time unit.
  
      Args:
          units (str): String specifying the units
              (one of ``'fs'``, ``'ps'``, ``'ns'``, ``'us'``, ``'ms'``, ``'sec'``).
  
      Returns:
          The the ``log10()`` of the scale factor for the time unit.
      """
      scale = {
          'fs' :    -15,
          'ps' :    -12,
          'ns' :     -9,
          'us' :     -6,
          'ms' :     -3,
          'sec':      0}
  
      units_lwr = units.lower()
      if units_lwr not in scale:
          raise ValueError("Invalid unit ({0}) provided".format(units))
      else:
          return scale[units_lwr]
  
  # Ctypes helper functions
  
  
  def pack(ctypes_obj):
      """Convert a :mod:`ctypes` structure into a Python string.
  
      Args:
          ctypes_obj (ctypes.Structure): The :mod:`ctypes` structure to convert to a string.
  
      Returns:
          New Python string containing the bytes from memory holding *ctypes_obj*.
      """
      return ctypes.string_at(ctypes.addressof(ctypes_obj),
                              ctypes.sizeof(ctypes_obj))
  
  
  def unpack(ctypes_obj, string, bytes=None):
      """Unpack a Python string into a :mod:`ctypes` structure.
  
      If the length of *string* is not the correct size for the memory
      footprint of the :mod:`ctypes` structure then the *bytes* keyword argument
      must be used.
  
      Args:
          ctypes_obj (ctypes.Structure): The :mod:`ctypes` structure to pack into.
          string (str):  String to copy over the *ctypes_obj* memory space.
          bytes (int, optional): Number of bytes to copy.
              Defaults to ``None``, meaning the length of *string* is used.
  
      Raises:
          :exc:`ValueError`: If length of *string* and size of *ctypes_obj*
              are not equal.
          :exc:`MemoryError`: If *bytes* is longer than size of *ctypes_obj*.
      """
      if bytes is None:
          if len(string) != ctypes.sizeof(ctypes_obj):
              raise ValueError("Attempt to unpack a string of size %d into a \
                  struct of size %d" % (len(string), ctypes.sizeof(ctypes_obj)))
          bytes = len(string)
  
      if bytes > ctypes.sizeof(ctypes_obj):
          raise MemoryError("Attempt to unpack %d bytes over an object \
                          of size %d" % (bytes, ctypes.sizeof(ctypes_obj)))
  
      ctypes.memmove(ctypes.addressof(ctypes_obj), string, bytes)
  
  
  import cocotb.ANSI as ANSI
  
  
  # A note on the use of latin1 in the deprecations below:
  # Latin1 is the only encoding `e` that satisfies
  # `all(chr(x).encode(e) == bytes([x]) for x in range(255))`
  # Our use of `ord` and `chr` throughout other bits of code make this the most
  # compatible choice of encoding. Under this convention, old code can be upgraded
  # by changing "binary" strings from `"\x12\x34"` to `b"\x12\x34"`.
  
  
  def _sane(x: bytes) -> str:
      r = ""
      for j in x
  
  [... the rest is omitted ...]
files: cocotb/utils.py