from hwt.synthesizer.dummyPlatform import DummyPlatform
from hwt.synthesizer.exceptions import IntfLvlConfErr
from hwt.synthesizer.interfaceLevel.interfaceUtils.utils import walkParams
from hwt.synthesizer.interfaceLevel.mainBases import UnitBase
from hwt.synthesizer.interfaceLevel.propDeclrCollector import PropDeclrCollector
from hwt.synthesizer.interfaceLevel.unitImplHelpers import UnitImplHelpers, \
    _default_param_updater
from hwt.synthesizer.rtlLevel.netlist import RtlNetlist
from hwt.doc_markers import internal

class Unit(UnitBase, PropDeclrCollector, UnitImplHelpers):
    """
    Container of the netlist with interfaces
    and internal hierarchical structure

    :cvar _serializeDecision: function to decide if Hdl object derived from
        this unit should be serialized or not, if None all is always serialized
    :cvar _PROTECTED_NAMES: set of names which can not be overridden
    :ivar _interfaces: all public interfaces
    :ivar _private_interfaces: all internal interfaces
        which are not accessible from outside of unit
    :ivar _units: all units defined on this obj
    :ivar _params: all params defined on this obj
    :ivar _parent: parent object (Unit instance)
    :ivar _lazyLoaded: container of rtl object which were lazy loaded
        in implementation phase (this object has to be returned
        from _toRtl of parent before it it's own objects)
    :ivar _targetPlatform: metainformations about target platform
    """

    _serializeDecision = None
    _PROTECTED_NAMES = set(["_PROTECTED_NAMES", "_interfaces",
                            "_units", "_params", "_parent",
                            "_lazyLoaded", "_ctx",
                            "_externInterf", "_targetPlatform"])

    def __init__(self):
        self._parent = None
        self._lazyLoaded = []
        self._ctx = RtlNetlist(self)

        self._loadConfig()

    @internal
    def _toRtl(self, targetPlatform: DummyPlatform):
        """
        synthesize all subunits, make connections between them,
        build entity and component for this unit
        """
        assert not self._wasSynthetised()

        self._targetPlatform = targetPlatform
        if not hasattr(self, "_name"):
            self._name = self._getDefaultName()

        for proc in targetPlatform.beforeToRtl:
            proc(self)

        self._ctx.params = self._buildParams()
        self._externInterf = []

        # prepare subunits
        for u in self._units:
            yield from u._toRtl(targetPlatform)

        for u in self._units:
            subUnitName = u._name
            u._signalsForMyEntity(self._ctx, "sig_" + subUnitName)

        # prepare signals for interfaces
        for i in self._interfaces:
            signals = i._signalsForInterface(self._ctx)
            if i._isExtern:
                self._externInterf.extend(signals)

        for proc in targetPlatform.beforeToRtlImpl:
            proc(self)
        self._loadMyImplementations()
        yield from self._lazyLoaded

        if not self._externInterf:
            raise IntfLvlConfErr(
                "Can not find any external interface for unit %s"
                "- unit without interfaces are not allowed"
                % self._name)

        for proc in targetPlatform.afterToRtlImpl:
            proc(self)

        yield from self._synthetiseContext(self._externInterf)
        self._checkArchCompInstances()

        for proc in targetPlatform.afterToRtl:
            proc(self)

    def _wasSynthetised(self):
        return self._ctx.synthesised

    @internal
    def _synthetiseContext(self, externInterf):
        # synthesize signal level context
        s = self._ctx.synthesize(
            self._name, externInterf, self._targetPlatform)
        self._entity = s[0]
        self._entity.__doc__ = self.__doc__
        self._entity.origin = self

        self._architecture = s[1]

        for intf in self._interfaces:
            if intf._isExtern:
                # reverse because other components
                # looks at this one from outside
                intf._reverseDirection()

        # connect results of synthesized context to interfaces of this unit
        self._boundInterfacesToEntity(self._interfaces)
        yield from s

        # after synthesis clean up interface so unit can be used elsewhere
        self._cleanAsSubunit()

    @internal
    def _loadInterface(self, i, isExtern):
        i._loadDeclarations()
        i._setAsExtern(isExtern)

    @internal
    def _loadDeclarations(self):
        """
        Load all declarations from _decl() method, recursively
        for all interfaces/units.
        """
        if not hasattr(self, "_interfaces"):
            self._interfaces = []
        if not hasattr(self, "_private_interfaces"):
            self._private_interfaces = []
        if not hasattr(self, "_units"):
            self._units = []
        self._setAttrListener = self._declrCollector
        self._declr()
        self._setAttrListener = None
        for i in self._interfaces:
            self._loadInterface(i, True)

        # if I am a unit load subunits
        for u in self._units:
            u._loadDeclarations()
        for p in self._params:
            p.setReadOnly()

    @internal
    def _registerIntfInImpl(self, iName, intf):
        """
        Register interface in implementation phase
        """
        self._registerInterface(iName, intf, isPrivate=True)
        self._loadInterface(intf, False)
        intf._signalsForInterface(self._ctx)

    @internal
    def _buildParams(self):
        # construct params for entity (generics)
        params = {}

        def addP(n, p):
            p.name = n
            if n in params:
                raise IntfLvlConfErr(
                    "Redefinition of generic '%s' while synthesis"
                    " old:%r, new:%r"
                    % (n, params[n], p))
            params[n] = p

        def nameForNestedParam(p):
            n = ""
            node = p
            while node is not self:
                if n == "":
                    n = node._name
                else:
                    n = node._name + "_" + n
                node = node._parent

            return n

        # collect params of this unit
        discoveredParams = set()
        for p in self._params:
            discoveredParams.add(p)
            addP(p.name, p)

        # collect params from interfaces
        for intf in self._interfaces:
            for p in walkParams(intf, discoveredParams):
                n = nameForNestedParam(p)
                addP(n, p)
                p._registerScope(n, self)

        return params

    def _getDefaultName(self):
        return self.__class__.__name__

    @internal
    def _checkArchCompInstances(self):
        cInstances = len(self._architecture.componentInstances)
        units = len(self._units)
        if cInstances != units:
            inRtl = set(
                map(lambda x: x.name, self._architecture.componentInstances))
            inIntf = set(map(lambda x: x._name + "_inst", self._units))
            if cInstances > units:
                raise IntfLvlConfErr(
                    "_toRtl unit(s) %s were found in rtl but were"
                    " not registered at %s"
                    % (str(inRtl - inIntf), self._name))
            elif cInstances < units:
                raise IntfLvlConfErr("_toRtl of %s: unit(s) %s were lost"
                                     % (self._name, str(inIntf - inRtl)))

    def _updateParamsFrom(self, otherObj, updater=_default_param_updater, exclude=None, prefix=""):
        """
        :note: doc in :func:`~hwt.synthesizer.interfaceLevel.propDeclCollector._updateParamsFrom`
        """
        PropDeclrCollector._updateParamsFrom(self, otherObj, updater, exclude, prefix)


from hwt.code import Concat
from hwt.synthesizer.vectorUtils import iterBits

def reverseByteOrder(signalOrVal):
    """
    Reverse byteorder (littleendian/bigendian) of signal or value
    """
    w = signalOrVal._dtype.bit_length()
    i = w
    items = []

    while i > 0:
        # take last 8 bytes or rest
        lower = max(i - 8, 0)
        items.append(signalOrVal[i:lower])
        i -= 8

    return Concat(*items)

def reversedBits(sigOrVal):
    return Concat(* list(iterBits(sigOrVal)))


from hwt.doc_markers import internal

@internal
def tryReduceAnd(sig, val):
    """
    Return sig and val reduced by & operator or None
    if it is not possible to statically reduce expression
    """
    m = sig._dtype.all_mask()
    if val._isFullVld():
        v = val.val
        if v == m:
            return sig
        elif v == 0:
            return val

@internal
def tryReduceOr(sig, val):
    """
    Return sig and val reduced by | operator or None
    if it is not possible to statically reduce expression
    """
    m = sig._dtype.all_mask()
    if not val.vldMask:
        return val

    if val._isFullVld():
        v = val.val
        if v == m:
            return val
        elif v == 0:
            return sig

@internal
def tryReduceXor(sig, val):
    """
    Return sig and val reduced by ^ operator or None
    if it is not possible to statically reduce expression
    """
    m = sig._dtype.all_mask()
    if not val.vldMask:
        return val

    if val._isFullVld():
        v = val.val
        if v == m:
            return ~sig
        elif v == 0:
            return sig


from hwt.hdl.architecture import Architecture
from hwt.hdl.entity import Entity
from hwt.hdl.ifContainter import IfContainer
from hwt.hdl.operator import Operator
from hwt.hdl.operatorDefs import OpDefinition
from hwt.hdl.switchContainer import SwitchContainer
from hwt.hdl.types.array import HArray
from hwt.hdl.types.bits import Bits
from hwt.hdl.types.bool import HBool
from hwt.hdl.types.enum import HEnum
from hwt.hdl.types.hdlType import HdlType
from hwt.hdl.types.integer import Integer
from hwt.hdl.value import Value
from hwt.serializer.exceptions import SerializerException
from hwt.serializer.exceptions import UnsupportedEventOpErr
from hwt.serializer.generic.context import SerializerCtx
from hwt.serializer.generic.indent import getIndent
from hwt.serializer.generic.nameScope import NameScope
from hwt.synthesizer.rtlLevel.mainBases import RtlSignalBase
from hwt.synthesizer.unit import Unit
from hwt.doc_markers import internal
from typing import Union
from hwt.synthesizer.rtlLevel.rtlSignal import RtlSignal

@internal
class CurrentUnitSwap():
    """
    Context manager which temporarilly swaps currentUnit property on specified context
    """
    def __init__(self, ctx, u):
        self.ctx = ctx
        self.u = u

    def __enter__(self):
        self.origUnit = self.ctx.currentUnit
        self.ctx.currentUnit = self.u

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.ctx.currentUnit = self.origUnit

class GenericSerializer():
    """
    Base class for serializers
    """
    @staticmethod
    def formatter(s):
        return s

    @classmethod
    def getBaseNameScope(cls):
        """
        Get root of name space
        """
        s = NameScope(False)
        s.setLevel(1)
        s[0].update(cls._keywords_dict)
        return s

    @classmethod
    def getBaseContext(cls):
        return SerializerCtx(cls.getBaseNameScope(), 0, None, None)

    @classmethod
    def asHdl(cls, obj, ctx: SerializerCtx):
        """
        Convert object to HDL string

        :param obj: object to serialize
        :param ctx: SerializerCtx instance
        """
        if isinstance(obj, RtlSignalBase):
            return cls.SignalItem(obj, ctx)
        elif isinstance(obj, Value):
            return cls.Value(obj, ctx)
        else:
            try:
                serFn = getattr(cls, obj.__class__.__name__)
            except AttributeError:
                raise SerializerException("Not implemented for", obj)
            return serFn(obj, ctx)

    @classmethod
    def Entity_prepare(cls, ent, ctx: SerializerCtx, serialize=True):
        if serialize:
            serializedGenerics = []
            serializedPorts = []

        scope = ctx.scope
        ent.generics.sort(key=lambda x: x.name)
        ent.ports.sort(key=lambda x: x.name)

        ent.name = scope.checkedName(ent.name, ent, isGlobal=True)
        for g in ent.generics:
            g.name = scope.checkedName(g.name, g)
            if serialize:
                serializedGenerics.append(cls.GenericItem(g, ctx))

        for p in ent.ports:
            p.name = scope.checkedName(p.name, p)
            p.getInternSig().name = p.name
            if serialize:
                serializedPorts.append(cls.PortItem(p, ctx))

        if serialize:
            return serializedGenerics, serializedPorts

    @classmethod
    def Entity(cls, ent: Entity, ctx: SerializerCtx):
        """
        Entity is just forward declaration of Architecture, it is not used
        in most HDL languages as there is no recursion in hierarchy
        """

        ent.name = ctx.scope.checkedName(ent.name, ent, isGlobal=True)
        return ""

    @classmethod
    def serializationDecision(cls, obj, serializedClasses,
                              serializedConfiguredUnits):
        """
        Decide if this unit should be serialized or not eventually fix name
        to fit same already serialized unit

        :param obj: object to serialize
        :param serializedClasses: dict {unitCls : unitobj}
        :param serializedConfiguredUnits: (unitCls, paramsValues) : unitObj
            where paramsValues are named tuple name:value
        """
        isDeclaration = isinstance(obj, Entity)
        isDefinition = isinstance(obj, Architecture)
        if isDeclaration:
            unit = obj.origin
        elif isDefinition:
            unit = obj.entity.origin
        else:
            return True

        assert isinstance(unit, Unit)
        sd = unit._serializeDecision
        if sd is None:
            return True
        else:
            prevPriv = serializedClasses.get(unit.__class__, None)
            seriazlize, nextPriv = sd(unit, obj, isDeclaration, prevPriv)
            serializedClasses[unit.__class__] = nextPriv
            return seriazlize

    @classmethod
    def HdlType(cls, typ: HdlType, ctx: SerializerCtx, declaration=False):
        """
        Serialize HdlType instance
        """
        if isinstance(typ, Bits):
            sFn = cls.HdlType_bits
        elif isinstance(typ, HEnum):
            sFn = cls.HdlType_enum
        elif isinstance(typ, HArray):
            sFn = cls.HdlType_array
        elif isinstance(typ, Integer):
            sFn = cls.HdlType_int
        elif isinstance(typ, HBool):
            sFn = cls.HdlType_bool
        else:
            raise NotImplementedError("type declaration is not implemented"
                                      " for type %s"
                                      % (typ.name))

        return sFn(typ, ctx, declaration=declaration)

    @classmethod
    def If(cls, *args, **kwargs):
        return cls.IfContainer(*args, **kwargs)

    @classmethod
    def IfContainer(cls, ifc: IfContainer, ctx: SerializerCtx):
        """
        Srialize IfContainer instance
        """
        childCtx = ctx.withIndent()

        def asHdl(statements):
            return [cls.asHdl(s, childCtx) for s in statements]

        try:
            cond = cls.condAsHdl(ifc.cond, True, ctx)
        except UnsupportedEventOpErr as e:
            cond = None

        if cond is None:
            assert not ifc.elIfs
            assert not ifc.ifFalse
            stmBuff = [cls.asHdl(s, ctx) for s in ifc.ifTrue]
            return "\n".join(stmBuff)

        elIfs = []
        ifTrue = ifc.ifTrue
        ifFalse = ifc.ifFalse
        if ifFalse is None:
            ifFalse = []

        for c, statements in ifc.elIfs:
            try:
                elIfs.append((cls.condAsHdl(c, True, ctx), asHdl(statements)))
            except UnsupportedEventOpErr as e:
                if len(ifc.elIfs) == 1 and not ifFalse:
                    # register expression is in valid format and this
                    # is just register with asynchronous reset or etc...
                    ifFalse = statements
                else:
                    raise e

        return cls.ifTmpl.render(
            indent=getIndent(ctx.indent),
            cond=cond,
            ifTrue=asHdl(ifTrue),
            elIfs=elIfs,
            ifFalse=asHdl(ifFalse))

    @classmethod
    def Switch(cls, *args, **kwargs):
        return cls.SwitchContainer(*args, **kwargs)

    @classmethod
    def FsmBuilder(cls, *args, **kwargs):
        return cls.SwitchContainer(*args, **kwargs)

    @classmethod
    def SwitchContainer(cls, sw: SwitchContainer, ctx: SerializerCtx):
        childCtx = ctx.withIndent(1)

        def asHdl(statements):
            return [cls.asHdl(s, childCtx) for s in statements]

        switchOn = cls.condAsHdl(sw.switchOn, False, ctx)

        cases = []
        for key, statements in sw.cases:
            key = cls.asHdl(key, ctx)

            cases.append((key, asHdl(statements)))

        if sw.default:
            cases.append((None, asHdl(sw.default)))

        return cls.switchTmpl.render(
            indent=getIndent(ctx.indent),
            switchOn=switchOn,
            cases=cases)

    @classmethod
    def _operand(cls, operand: Union[RtlSignal, Value], operator: OpDefinition, ctx: SerializerCtx):
        s = cls.asHdl(operand, ctx)
        if isinstance(operand, RtlSignalBase):
            try:
                o = operand.singleDriver()
                if o.operator != operator and\
                        cls.opPrecedence[o.operator] <= cls.opPrecedence[operator]:
                    return "(%s)" % s
            except Exception:
                pass
        return s

    @classmethod
    def _bin_op(cls, operator, op_str, ctx: SerializerCtx, ops):
        return op_str.join(map(lambda operand: cls._operand(operand, operator, ctx), ops))


from hwt.hdl.constants import Time
from hwt.simulator.agentBase import AgentBase

def pullDownAfter(sig, initDelay=6 * Time.ns):
    """
    :return: simulation driver which keeps signal value high for initDelay
        then it sets value to 0
    """
    def _pullDownAfter(s):
        s.write(True, sig)
        yield s.wait(initDelay)
        s.write(False, sig)

    return _pullDownAfter

def pullUpAfter(sig, initDelay=6 * Time.ns):
    """
    :return: Simulation driver which keeps signal value low for initDelay then
        it sets value to 1
    """
    def _pullDownAfter(s):
        s.write(False, sig)
        yield s.wait(initDelay)
        s.write(True, sig)

    return _pullDownAfter

class PullUpAgent(AgentBase):
    def __init__(self, intf, initDelay=6 * Time.ns):
        self.initDelay = initDelay
        self.intf = intf
        self.data = []

    def driver(self, sim):
        sig = self.intf
        sim.write(False, sig)
        yield sim.wait(self.initDelay)
        sim.write(True, sig)

class PullDownAgent(AgentBase):
    def __init__(self, intf, initDelay=6 * Time.ns):
        self.initDelay = initDelay
        self.intf = intf
        self.data = []

    def driver(self, sim):
        sig = self.intf
        sim.write(True, sig)
        yield sim.wait(self.initDelay)
        sim.write(False, sig)


from hwt.doc_markers import internal
from hwt.hdl.operator import Operator
from hwt.hdl.operatorDefs import AllOps

@internal
def getBaseCond(c):
    """
    if is negated return original cond and negated flag
    """
    isNegated = False
    try:
        drivers = c.drivers
    except AttributeError:
        return (c, isNegated)

    if len(drivers) == 1:
        d = list(c.drivers)[0]
        if isinstance(d, Operator) and d.operator == AllOps.NOT:
            c = d.operands[0]
            isNegated = True

    return (c, isNegated)


from hwt.bitmask import mask
from hwt.hdl.types.bits import Bits
from hwt.simulator.types.simBitsConversions import convertSimBits__val
from typing import Union
from hwt.doc_markers import internal

__simBitsTCache = {}

@internal
def simBitsT(width: int, signed: Union[bool, None]):
    """
    Construct SimBitsT with cache
    """
    k = (width, signed)
    try:
        return __simBitsTCache[k]
    except KeyError:
        t = SimBitsT(width, signed)
        __simBitsTCache[k] = t
        return t

@internal
class SimBitsT(Bits):
    """
    Simplified Bits type for simulation purposes
    """

    def __init__(self, width, signed):
        self._widthVal = width
        self.signed = signed
        self._allMask = mask(self._widthVal)
        self.forceVector = False

    def __eq__(self, other):
        return isinstance(other, Bits) and other._widthVal == self._widthVal\
            and self.signed == other.signed

    def __hash__(self):
        return hash((self._widthVal, self.signed))

    def convert(self, sigOrVal, toType):
        return convertSimBits__val(self, sigOrVal, toType)

    def __repr__(self, indent=0, withAddr=None, expandStructs=False):
        return "<SimBitsT, signed:%r, %dbits>" % (self.signed, self._widthVal)

SIM_BIT = SimBitsT(1, None)


from hwt.hdl.value import Value, areValues
from hwt.serializer.generic.indent import getIndent

class StructValBase(Value):
    """
    Base class for values for structure types.
    Every structure type has it's own value class derived from this.
    """
    __slots__ = []

    def __init__(self, val, typeObj, skipCheck=False):
        """
        :param val: None or dict {field name: field value}
        :param typeObj: instance of String HdlType
        :param skipCheck: flag to skip field name consystency in val
        """
        self._dtype = typeObj
        self.updateTime = -1
        if not skipCheck and val is not None:
            assert set(self.__slots__).issuperset(set(val.keys())), \
                set(val.keys()).difference(set(self.__slots__))

        for f in self._dtype.fields:
            if f.name is None:
                continue
            if val is None:
                v = None
            else:
                v = val.get(f.name, None)

            if not isinstance(v, Value):
                v = f.dtype.fromPy(v)

            setattr(self, f.name, v)

    def clone(self):
        d = {}
        for f in self._dtype.fields:
            if f.name is None:
                continue

            v = getattr(self, f.name).clone()
            d[f.name] = v

        return self.__class__(d, self._dtype, skipCheck=True)

    @classmethod
    def fromPy(cls, val, typeObj, vldMask=None):
        """
        :param val: None or dict {field name: field value}
        :param typeObj: instance of String HdlType
        :param vldMask: if is None validity is resolved from val
            if is 0 value is invalidated
            if is 1 value has to be valid
        """
        if vldMask == 0:
            val = None
        return cls(val, typeObj)

    def toPy(self):
        if not self._isFullVld():
            raise ValueError("Value of %r is not fully defined" % self)
        d = {}
        for f in self._dtype.fields:
            if f.name is not None:
                val = getattr(self, f.name).toPy()
                d[f.name] = val
        return d

    def __eq__(self, other):
        if areValues(self, other):
            if self._dtype == other._dtype:
                for f in self._dtype.fields:
                    isPadding = f.name is None
                    if not isPadding:
                        sf = getattr(self, f.name)
                        of = getattr(other, f.name)
                        if not (sf == of):
                            return False
                return True
            else:
                return False
        else:
            return super(Value, self).__eq__(other)

    def __repr__(self, indent=0):
        buff = ["{"]
        indentOfFields = getIndent(indent + 1)

        for f in self._dtype.fields:
            if f.name is not None:
                val = getattr(self, f.name)
                try:
                    v = val.__repr__(indent=indent + 1)
                except TypeError:
                    v = repr(val)

                buff.append("%s%s: %s" % (indentOfFields, f.name, v))
        buff.append(getIndent(indent) + "}")
        return ("\n").join(buff)


from hwt.hdl.constants import DIRECTION
from hwt.hdl.types.bits import Bits
from hwt.hdl.types.defs import BIT, BIT_N
from hwt.interfaces.agents.bramPort import BramPortAgent
from hwt.interfaces.agents.bramPort import BramPort_withoutClkAgent
from hwt.interfaces.agents.clk import OscilatorAgent
from hwt.interfaces.agents.fifo import FifoReaderAgent
from hwt.interfaces.agents.fifo import FifoWriterAgent
from hwt.interfaces.agents.handshaked import HandshakeSyncAgent
from hwt.interfaces.agents.handshaked import HandshakedAgent
from hwt.interfaces.agents.rdSynced import RdSyncedAgent
from hwt.interfaces.agents.regCntrl import RegCntrlAgent
from hwt.interfaces.agents.rst import PullDownAgent
from hwt.interfaces.agents.rst import PullUpAgent
from hwt.interfaces.agents.signal import SignalAgent
from hwt.interfaces.agents.vldSynced import VldSyncedAgent
from hwt.interfaces.signalOps import SignalOps
from hwt.synthesizer.interface import Interface
from hwt.synthesizer.param import Param

D = DIRECTION

class Signal(SignalOps, Interface):
    """
    Basic wire interface
    """

    def __init__(self,
                 masterDir=D.OUT,
                 dtype=BIT,
                 loadConfig=True):
        super().__init__(masterDir=masterDir,
                         loadConfig=loadConfig)
        self._dtype = dtype

    def _initSimAgent(self):
        self._ag = SignalAgent(self)

def VectSignal(width,
               signed=None,
               masterDir=D.OUT,
               loadConfig=True):
    """
    Create basic :class:`.Signal` interface where type is vector
    """
    return Signal(masterDir,
                  Bits(width, signed, forceVector=True),
                  loadConfig)

class Clk(Signal):
    """
    Basic :class:`.Signal` interface which is interpreted as clock signal
    """
    DEFAULT_FREQ = int(100e6)

    def _getIpCoreIntfClass(self):
        from hwt.interfaces.std_ip_defs import IP_Clk
        return IP_Clk

    def _initSimAgent(self):
        self._ag = OscilatorAgent(self)

class Rst(Signal):
    """
    Basic :class:`.Signal` interface which is interpreted as reset signal
    """

    def _getIpCoreIntfClass(self):
        from hwt.interfaces.std_ip_defs import IP_Rst
        return IP_Rst

    def _initSimAgent(self):
        self._ag = PullDownAgent(self)

class Rst_n(Signal):
    """
    Basic :class:`.Signal` interface which is interpreted as reset signal
    with negative polarity (active in 0)
    """

    def __init__(self,
                 masterDir=D.OUT,
                 dtype=BIT_N,
                 loadConfig=True):
        super(Rst_n, self).__init__(masterDir=masterDir,
                                    dtype=dtype,
                                    loadConfig=loadConfig)

    def _getIpCoreIntfClass(self):
        from hwt.interfaces.std_ip_defs import IP_Rst_n
        return IP_Rst_n

    def _initSimAgent(self):
        self._ag = PullUpAgent(self)

class VldSynced(Interface):
    """
    Interface data+valid signal, if vld=1 then data are valid and slave should
    accept them
    """

    def _config(self):
        self.DATA_WIDTH = Param(64)

    def _declr(self):
        self.data = VectSignal(self.DATA_WIDTH)
        self.vld = s()

    def _initSimAgent(self):
        self._ag = VldSyncedAgent(self)

class RdSynced(Interface):
    """
    Interface data+ready signal, if rd=1 then slave has read data and master
    should actualize data
    """

    def _config(self):
        self.DATA_WIDTH = Param(64)

    def _declr(self):
        self.data = VectSignal(self.DATA_WIDTH)
        self.rd = s(masterDir=D.IN)

    def _initSimAgent(self):
        self._ag = RdSyncedAgent(self)

class Handshaked(VldSynced):
    """
    Interface data+ready+valid signal, if rd=1 slave is ready to accept data,
    if vld=1 master is sending data,
    if rd=1 and vld=1 then data is transfered otherwise master
    and slave has to wait on each other

    :attention: one rd/vld is set it must not go down until transaction is made
    """

    def _declr(self):
        super()._declr()
        self.rd = s(masterDir=D.IN)

    def _initSimAgent(self):
        self._ag = HandshakedAgent(self)

class HandshakeSync(Interface):
    """
    Only synchronization interface, like vld+rd signal with meaning
    like in :class:`.Handshaked` interface

    :ivar rd: when high slave is ready to receive data
    :ivar vld: when high master is sending data to slave

    transaction happens when both ready and valid are high

    """

    def _declr(self):
        self.vld = s()
        self.rd = s(masterDir=D.IN)

    def _initSimAgent(self):
        self._ag = HandshakeSyncAgent(self)

class ReqDoneSync(Interface):
    """
    Synchronization interface, if req=1 slave begins operation and when
    it's done it asserts done=1 for one clk tick req does not need to stay high
    """

    def _declr(self):
        self.req = s()
        self.done = s(masterDir=D.IN)

class BramPort_withoutClk(Interface):
    """
    Basic BRAM port
    """

    def _config(self):
        self.ADDR_WIDTH = Param(32)
        self.DATA_WIDTH = Param(64)

    def _declr(self):
        self.addr = VectSignal(self.ADDR_WIDTH)
        self.din = VectSignal(self.DATA_WIDTH)
        self.dout = VectSignal(self.DATA_WIDTH, masterDir=D.IN)
        self.en = s()
        self.we = s()

    def _getWordAddrStep(self):
        """
        :return: size of one word in unit of address
        """
        return 1

    def _getAddrStep(self):
        """
        :return: how many bits is one unit of address (f.e. 8 bits for
            char * pointer, 36 for 36 bit bram)
        """
        return int(self.DATA_WIDTH)

    def _getIpCoreIntfClass(self):
        from hwt.interfaces.std_ip_defs import IP_BlockRamPort
        return IP_BlockRamPort

    def _initSimAgent(self):
        self._ag = BramPort_withoutClkAgent(self)

class BramPort(BramPort_withoutClk):
    """
    BRAM port with it's own clk
    """

    def _declr(self):
        self.clk = s(masterDir=D.OUT)
        with self._associated(clk=self.clk):
            super()._declr()

        self._associatedClk = self.clk

    def _initSimAgent(self):
        self._ag = BramPortAgent(self)

class FifoWriter(Interface):
    def _config(self):
        self.DATA_WIDTH = Param(8)

    def _declr(self):
        self.en = s()
        self.wait = s(masterDir=D.IN)
        self.data = VectSignal(self.DATA_WIDTH)

    def _initSimAgent(self):
        self._ag = FifoWriterAgent(self)

class FifoReader(FifoWriter):
    def _declr(self):
        super()._declr()
        self.en._masterDir = D.IN
        self.wait._masterDir = D.OUT

    def _initSimAgent(self):
        self._ag = FifoReaderAgent(self)

class RegCntrl(Interface):
    """
    Register control interface, :class:`.Signal` for read, :class:`.VldSynced`
    for write
    """

    def _config(self):
        self.DATA_WIDTH = Param(8)

    def _declr(self):
        self.din = VectSignal(self.DATA_WIDTH, masterDir=D.IN)
        with self._paramsShared():
            self.dout = VldSynced()

    def _initSimAgent(self):
        self._ag = RegCntrlAgent(self)

s = Signal


class ConstCache(object):
    """
    Container of constants for serializer.
    Used to extract constants as constant variables.
    """

    def __init__(self, nameCheckFn):
        self.nameCheckFn = nameCheckFn

        # {value:usedName}
        self._cache = {}

    def getConstName(self, val):
        """
        Get constant name for value
        name of constant is reused if same value was used before
        """
        try:
            return self._cache[val]
        except KeyError:
            if isinstance(val.val, int):
                name = "const_%d_" % val.val
            else:
                name = "const_"

            c = self.nameCheckFn(name, val)
            self._cache[val] = c
            return c


from typing import Tuple, List, Dict

from hwt.hdl.sensitivityCtx import SensitivityCtx
from hwt.hdl.statements import isSameHVal, HdlStatement, areSameHVals
from hwt.hdl.value import Value
from hwt.synthesizer.rtlLevel.mainBases import RtlSignalBase
from hwt.doc_markers import internal

class Assignment(HdlStatement):
    """
    Assignment container

    :ivar src: source
    :ivar dst: destination signal
    :ivar indexes: description of index selector on dst
        (list of Index/Slice objects) (f.e. [[0], [1]] means  dst[0][1])

    :cvar __instCntr: counter used for generating instance ids
    :ivar _instId: internaly used only for intuitive sorting of statements
    """
    __instCntr = 0

    def __init__(self, src, dst, indexes=None, virtualOnly=False,
                 parentStm=None,
                 sensitivity=None,
                 is_completly_event_dependent=False):
        """
        :param dst: destination to assign to
        :param src: source which is assigned from
        :param indexes: description of index selector on dst
            (list of Index/Slice objects) (f.e. [[0], [1]] means  dst[0][1])
        :param virtualOnly: flag indicates that this assignments
            is only virtual and should not be added into
            netlist, because it is only for internal notation
        """
        super(Assignment, self).__init__(
            parentStm,
            sensitivity,
            is_completly_event_dependent)
        self.src = src
        isReal = not virtualOnly

        if not isinstance(src, Value):
            self._inputs.append(src)
            if isReal:
                src.endpoints.append(self)

        self.dst = dst
        if not isinstance(dst, Value):
            self._outputs.append(dst)
            if isReal:
                dst.drivers.append(self)

        self.indexes = indexes
        if indexes:
            for i in indexes:
                if not isinstance(i, Value):
                    self._inputs.append(i)
                    if isReal:
                        i.endpoints.append(self)

        self._instId = Assignment._nextInstId()

        if not virtualOnly:
            dst.ctx.statements.add(self)

    @internal
    def _cut_off_drivers_of(self, sig: RtlSignalBase):
        """
        Cut off statements which are driver of specified signal
        """
        if self.dst is sig:
            self.parentStm = None
            return self
        else:
            return None

    @internal
    def _discover_enclosure(self) -> None:
        assert self._enclosed_for is None
        self._enclosed_for = set()
        self._enclosed_for.update(self._outputs)

    @internal
    def _discover_sensitivity(self, seen: set) -> None:
        assert self._sensitivity is None
        ctx = self._sensitivity = SensitivityCtx()

        casualSensitivity = set()
        for inp in self._inputs:
            if inp not in seen:
                seen.add(inp)
                inp._walk_sensitivity(casualSensitivity, seen, ctx)
        self._sensitivity.extend(casualSensitivity)

    @internal
    def _fill_enclosure(self, enclosure: Dict[RtlSignalBase, HdlStatement]):
        """
        Enclosure is never requiered
        """
        pass

    @internal
    def _iter_stms(self):
        """
        Iterate all statements in this statement
        """
        return
        yield

    @internal
    def _on_parent_event_dependent(self):
        """
        After parrent statement become event dependent
        """
        if not self._is_completly_event_dependent:
            self._is_completly_event_dependent = True

    @internal
    def _try_reduce(self) -> Tuple[List["HdlStatement"], bool]:
        return [self, ], False

    @internal
    def _is_mergable(self, other: HdlStatement) -> bool:
        return isinstance(other, self.__class__)

    def isSame(self, other):
        if isinstance(other, self.__class__):
            if isSameHVal(self.dst, other.dst)\
                    and isSameHVal(self.src, other.src)\
                    and areSameHVals(self.indexes, other.indexes):
                return True
        return False

    @internal
    @classmethod
    def _nextInstId(cls):
        """
        Get next instance id
        """
        i = cls.__instCntr
        cls.__instCntr += 1
        return i

    @internal
    def seqEval(self):
        """Sequentially evaluate this assignment"""
        self.dst._val = self.src.staticEval()


from typing import Callable, Tuple, Generator, Union, Optional

from hwt.hdl.types.array import HArray
from hwt.hdl.types.bits import Bits
from hwt.hdl.types.hdlType import HdlType
from hwt.hdl.types.struct import HStruct, HStructField
from hwt.hdl.types.union import HUnion
from hwt.pyUtils.arrayQuery import iter_with_last
from hwt.synthesizer.param import evalParam
from hwt.hdl.types.stream import HStream
from hwt.doc_markers import internal

def _default_shouldEnterFn(transTmpl: 'TransTmpl') -> Tuple[bool, bool]:
    return (bool(transTmpl.children), not bool(transTmpl.children))

class _DummyIteratorCtx(object):
    """
    Dummy version of :class:`.ObjIteratorCtx`
    """
    def __call__(self, prop):
        return self

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        pass

class ObjIteratorCtx(object):
    """
    Object Iterator context

    Allows to walk object properties and keep track of it

    :note: :class:`.TransTmpl` uses this object to walk other object together with structure type
        this is useful when you need to walk generated interface together with type
        from which it was generated from

    :ivar actual: actual selected object
    :ivar parent: list of collected parent of this object
    :ivar onParentNames: list, str for children which are properties,
        int for children which are items of parent
    """

    def __init__(self, obj):
        self.actual = obj
        self.parents = []
        self.onParentNames = []
        self.nextProp = None

    def __call__(self, prop: Union[str, int]):
        """
        Prepare to enter child property or item in sequence

        :prop: str if entering a property, int if entering an item of sequence
        """
        self.nextProp = prop
        return self

    def __enter__(self):
        """
        Enter child property or item in sequence
        """
        prop = self.nextProp
        self.nextProp = None
        a = self.actual
        if isinstance(prop, int):
            child = a[prop]
        else:
            child = getattr(a, prop)

        self.parents.append(a)
        self.onParentNames.append(prop)
        self.actual = child

        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        """
        Move back to parent
        """
        self.actual = self.parents.pop()
        self.onParentNames.pop()

class TransTmpl(object):
    """
    Container of informations about frames generated from any HType
    (HStruct etc.)

    * contains precalculated address range for all members of type

    :ivar dtype: type of this item
    :ivar bitAddr: offset of start of this item in bits
    :ivar parent: object which generated this item, optional TransTmpl
    :ivar origin: object which was template for generating of this item
    :ivar itemCnt: if this transaction template is for arry this is
        item count for array
    :ivar childrenAreChoice: flag which tells if childrens are sequence
        or only one of them can be used in same time
    """

    def __init__(self, dtype: HdlType, bitAddr: int=0,
                 parent: Optional['TransTmpl']=None,
                 origin: Optional[HStructField]=None):
        self.parent = parent
        if origin is None:
            origin = dtype

        self.origin = origin
        self.dtype = dtype
        self.children = []
        self._loadFromHType(dtype, bitAddr)

    @internal
    def _loadFromArray(self, dtype: HdlType, bitAddr: int) -> int:
        """
        Parse HArray type to this transaction template instance

        :return: address of it's end
        """
        self.itemCnt = evalParam(dtype.size).val
        self.children = TransTmpl(
            dtype.elmType, 0, parent=self, origin=self.origin)
        return bitAddr + self.itemCnt * self.children.bitAddrEnd

    @internal
    def _loadFromBits(self, dtype: HdlType, bitAddr: int):
        """
        Parse Bits type to this transaction template instance

        :return: address of it's end
        """
        return bitAddr + dtype.bit_length()

    @internal
    def _loadFromHStruct(self, dtype: HdlType, bitAddr: int):
        """
        Parse HStruct type to this transaction template instance

        :return: address of it's end
        """
        for f in dtype.fields:
            t = f.dtype
            origin = f
            isPadding = f.name is None

            if isPadding:
                width = t.bit_length()
                bitAddr += width
            else:
                fi = TransTmpl(t, bitAddr, parent=self, origin=origin)
                self.children.append(fi)
                bitAddr = fi.bitAddrEnd

        return bitAddr

    @internal
    def _loadFromUnion(self, dtype: HdlType, bitAddr: int) -> int:
        """
        Parse HUnion type to this transaction template instance

        :return: address of it's end
        """
        for field in dtype.fields.values():
            ch = TransTmpl(field.dtype, 0, parent=self, origin=field)
            self.children.append(ch)
        return bitAddr + dtype.bit_length()

    @internal
    def _loadFromHStream(self, dtype: HStream, bitAddr: int) -> int:
        """
        Parse HUnion type to this transaction template instance

        :return: address of it's end
        """
        ch = TransTmpl(dtype.elmType, 0, parent=self, origin=self.origin)
        self.children.append(ch)
        return bitAddr + dtype.elmType.bit_length()

    def _loadFromHType(self, dtype: HdlType, bitAddr: int) -> None:
        """
        Parse any HDL type to this transaction template instance
        """
        self.bitAddr = bitAddr
        childrenAreChoice = False
        if isinstance(dtype, Bits):
            ld = self._loadFromBits
        elif isinstance(dtype, HStruct):
            ld = self._loadFromHStruct
        elif isinstance(dtype, HArray):
            ld = self._loadFromArray
        elif isinstance(dtype, HStream):
            ld = self._loadFromHStream
        elif isinstance(dtype, HUnion):
            ld = self._loadFromUnion
            childrenAreChoice = True
        else:
            raise TypeError("expected instance of HdlType", dtype)

        self.bitAddrEnd = ld(dtype, bitAddr)
        self.childrenAreChoice = childrenAreChoice

    def getItemWidth(self) -> int:
        """
        Only for transactions derived from HArray

        :return: width of item in original array
        """
        if not isinstance(self.dtype, HArray):
            raise TypeError()
        return (self.bitAddrEnd - self.bitAddr) // self.itemCnt

    def bit_length(self) -> int:
        """
        :return: number of bits in this transaction
        """
        return self.bitAddrEnd - self.bitAddr

    def walkFlatten(self, offset: int=0,
                    shouldEnterFn=_default_shouldEnterFn,
                    otherObjItCtx: ObjIteratorCtx =_DummyIteratorCtx()
                    ) -> Generator[
            Union[Tuple[Tuple[int, int], 'TransTmpl'], 'OneOfTransaction'],
            None, None]:
        """
        Walk fields in instance of TransTmpl

        :param offset: optional offset for all children in this TransTmpl
        :param shouldEnterFn: function (transTmpl) which returns True
            when field should be split on it's children
        :param shouldEnterFn: function(transTmpl) which should return
            (shouldEnter, shouldUse) where shouldEnter is flag that means
            iterator should look inside of this actual object
            and shouldUse flag means that this field should be used
            (=generator should yield it)
        :return: generator of tuples ((startBitAddress, endBitAddress),
            TransTmpl instance)
        """

        t = self.dtype
        base = self.bitAddr + offset
        end = self.bitAddrEnd + offset

        shouldEnter, shouldYield = shouldEnterFn(self)
        if shouldYield:
            yield ((base, end), self)

        if shouldEnter:
            if isinstance(t, Bits):
                pass
            elif isinstance(t, HStruct):
                for ch in self.children:
                    with otherObjItCtx(ch.origin.name):
                        yield from ch.walkFlatten(
                            offset,
                            shouldEnterFn,
                            otherObjItCtx)
            elif isinstance(t, HArray):
                itemSize = (self.bitAddrEnd - self.bitAddr) // self.itemCnt
                for i in range(self.itemCnt):
                    with otherObjItCtx(i):
                        yield from self.children.walkFlatten(
                            base + i * itemSize,
                            shouldEnterFn,
                            otherObjItCtx)
            elif isinstance(t, HUnion):
                yield OneOfTransaction(self, offset, shouldEnterFn,
                                       self.children)
            elif isinstance(t, HStream):
                assert len(self.children) == 1
                yield StreamTransaction(self, offset, shouldEnterFn,
                                        self.children[0])
            else:
                raise TypeError(t)

    def __repr__(self, offset: int=0):
        offsetStr = "".join(["    " for _ in range(offset)])

        try:
            name = self.origin.name
        except AttributeError:
            name = None

        if name:
            name = " name:%s," % name
        else:
            name = ""

        s = "%s<TransTmpl%s start:%d, end:%d" % (offsetStr, name,
                                                 self.bitAddr, self.bitAddrEnd)
        if isinstance(self.dtype, HArray):
            s += ", itemCnt:%d" % (self.itemCnt) + "\n"
            s += self.children.__repr__(offset=offset + 1) + "\n"
            s += offsetStr + ">"
            return s
        elif not self.children:
            return s + ">"

        buff = [s, ]
        for isLast, child in iter_with_last(self.children):
            buff.append(child.__repr__(offset=offset + 1))
            if self.childrenAreChoice and not isLast:
                buff.append(offsetStr + "    <OR>")

        buff.append(offsetStr + ">")
        return "\n".join(buff)

class OneOfTransaction(object):
    """
    Container of possible transactions for transactions deriverd
    from HUnion type

    :ivar parent: parent TransTmpl instance
    :ivar offset: bit addr offset in parent type structure
    :ivar shouldEnterFn: function(transTmpl) which should
        return (shouldEnter, shouldUse)
        where shouldEnter is flag that means iterator should look
        inside of this actual object and shouldUse flag means that this field
        should be used (=generator should yield it)
    :ivar possibleTransactions: tuple of TransTmpl instances
        from which only one can be used in same time
    """

    def __init__(self, parent: TransTmpl,
                 offset: int,
                 shouldEnterFn: Callable[[TransTmpl], Tuple[bool, bool]],
                 possibleTransactions: Tuple[TransTmpl]):
        self.parent = parent
        self.offset = offset
        self.shouldEnterFn = shouldEnterFn
        self.possibleTransactions = possibleTransactions

    def walkFlattenChilds(self) -> Generator[
            Union[Tuple[Tuple[int, int], TransTmpl], 'OneOfTransaction'],
            None, None]:
        """
        :return: generator of generators of tuples
            ((startBitAddress, endBitAddress), TransTmpl instance)
            for each possiblility in this transaction
        """
        for p in self.possibleTransactions:
            yield p.walkFlatten(offset=self.offset,
                                shouldEnterFn=self.shouldEnterFn)

class StreamTransaction(object):
    """
    Container of informations about stream transaction which is described
    by HStream HdlType
    """
    def __init__(self, parent: TransTmpl,
                 offset: int,
                 shouldEnterFn: Callable[[TransTmpl], Tuple[bool, bool]],
                 child: TransTmpl):
        self.parent = parent
        self.offset = offset
        self.shouldEnterFn = shouldEnterFn
        self.child = child

    def walkFlatten(self, *args, **kwargs):
        """
        :note: doc in :meth:`.TransTmpl.walkFlatten`
        """
        return self.child.walkFlatten(*args, **kwargs)


from hwt.bitmask import mask
from hwt.doc_markers import internal
from hwt.hdl.operator import Operator
from hwt.hdl.operatorDefs import AllOps
from hwt.hdl.types.bits import Bits
from hwt.hdl.types.defs import BOOL
from hwt.hdl.types.integer import Integer
from hwt.hdl.types.typeCast import toHVal
from hwt.hdl.value import Value, areValues

# internal
BoolVal = BOOL.getValueCls()

@internal
def signFix(val, width):
    """
    Convert negative int to positive int which has same bits set
    """
    if val > 0:
        msb = 1 << (width - 1)
        if val & msb:
            val -= mask(width) + 1
    return val

@internal
def bitsCmp__val(self, other, op, evalFn):
    ot = other._dtype

    w = self._dtype._widthVal
    assert w == ot._widthVal, "%d, %d" % (w, ot._widthVal)

    vld = self.vldMask & other.vldMask
    _vld = vld == mask(w)
    res = evalFn(self.val, other.val) and _vld
    updateTime = max(self.updateTime, other.updateTime)

    return BoolVal(res, BOOL, int(_vld), updateTime)

# dictionary which hold information how to change operator after
# operands were swapped
CMP_OP_REVERSE = {
    AllOps.EQ: AllOps.EQ,  # (a == b) == (b == a)
    AllOps.GT: AllOps.LT,  # (a > b)  == (b < a)
    AllOps.LT: AllOps.GT,  # (a < b)  == (b > a)
    AllOps.GE: AllOps.LE,  # (a >= b) == (b <= a)
    AllOps.LE: AllOps.GE,  # (a <= b) == (b >= a)
}

@internal
def bitsCmp_detect_useless_cmp(op0, op1, op):
    v = int(op1)
    width = op1._dtype.bit_length()
    if op0._dtype.signed:
        min_val = -1 if width == 1 else mask(width - 1) - 1
        max_val = 0 if width == 1 else mask(width - 1)
    else:
        min_val = 0
        max_val = mask(width)

    if v == min_val:
        # value can not be lower than min_val
        if op == AllOps.GE:
            # -> always True
            return BOOL.fromPy(1, 1)
        elif op == AllOps.LT:
            # -> always False
            return BOOL.fromPy(0, 1)
        elif op == AllOps.LE:
            # convert <= to == to highlight the real function
            return AllOps.EQ
    else:
        if v == max_val:
            # value can not be greater than max_val
            if op == AllOps.GT:
                # always False
                return BOOL.fromPy(0, 1)
            elif op == AllOps.LE:
                # always True
                return BOOL.fromPy(1, 1)
            elif op == AllOps.GE:
                # because value can not be greater than max
                return AllOps.EQ

@internal
def bitsCmp(self, other, op, evalFn=None):
    """
    :attention: If other is Bool signal convert this to bool (not ideal,
        due VHDL event operator)
    """
    other = toHVal(other)
    t = self._dtype
    ot = other._dtype

    iamVal = isinstance(self, Value)
    otherIsVal = isinstance(other, Value)

    if evalFn is None:
        evalFn = op._evalFn

    if iamVal and otherIsVal:
        if ot == BOOL:
            self = self._auto_cast(BOOL)
        elif t == ot:
            pass
        elif isinstance(ot, Integer):
            other = other._auto_cast(t)
        else:
            raise TypeError("Values of types (%r, %r) are not comparable" % (
                self._dtype, other._dtype))

        return bitsCmp__val(self, other, op, evalFn)
    else:
        if ot == BOOL:
            self = self._auto_cast(BOOL)
        elif t == ot:
            pass
        elif isinstance(ot, Integer):
            other = other._auto_cast(self._dtype)
        else:
            raise TypeError("Values of types (%r, %r) are not comparable" % (
                self._dtype, other._dtype))

        # try to reduce useless cmp
        res = None
        if otherIsVal and other._isFullVld():
            res = bitsCmp_detect_useless_cmp(self, other, op)
        elif iamVal and self._isFullVld():
            res = bitsCmp_detect_useless_cmp(other, self, CMP_OP_REVERSE[op])

        if res is None:
            pass
        elif isinstance(res, Value):
            return res
        else:
            assert res == AllOps.EQ, res
            op = res

        return Operator.withRes(op, [self, other], BOOL)

@internal
def bitsBitOp__val(self, other, op, getVldFn):
    w = self._dtype.bit_length()
    assert w == other._dtype.bit_length()

    vld = getVldFn(self, other)
    res = op._evalFn(self.val, other.val) & vld
    updateTime = max(self.updateTime, other.updateTime)
    if self._dtype.signed:
        res = signFix(res, w)

    return self.__class__(res, self._dtype, vld, updateTime)

@internal
def bitsBitOp(self, other, op, getVldFn, reduceCheckFn):
    """
    :attention: If other is Bool signal, convert this to bool
        (not ideal, due VHDL event operator)
    """
    other = toHVal(other)

    iamVal = isinstance(self, Value)
    otherIsVal = isinstance(other, Value)

    if iamVal and otherIsVal:
        other = other._auto_cast(self._dtype)
        return bitsBitOp__val(self, other, op, getVldFn)
    else:
        if other._dtype == BOOL:
            self = self._auto_cast(BOOL)
            return op._evalFn(self, other)
        elif self._dtype == other._dtype:
            pass
        else:
            raise TypeError("Can not apply operator %r (%r, %r)" %
                            (op, self._dtype, other._dtype))

        if otherIsVal:
            r = reduceCheckFn(self, other)
            if r is not None:
                return r

        elif iamVal:
            r = reduceCheckFn(other, self)
            if r is not None:
                return r

        return Operator.withRes(op, [self, other], self._dtype)

@internal
def bitsArithOp__val(self, other, op):
    v = self.clone()
    self_vld = self._isFullVld()
    other_vld = other._isFullVld()

    v.val = op._evalFn(self.val, other.val)

    w = v._dtype.bit_length()
    if self._dtype.signed:
        _v = v.val
        _max = mask(w - 1)
        _min = -_max - 1
        if _v > _max:
            _v = _min + (_v - _max - 1)
        elif _v < _min:
            _v = _max - (_v - _min + 1)

        v.val = _v
    else:
        v.val &= mask(w)

    if self_vld and other_vld:
        v.vldMask = mask(w)
    else:
        v.vldMask = 0

    v.updateTime = max(self.updateTime, other.updateTime)
    return v

@internal
def bitsArithOp(self, other, op):
    other = toHVal(other)
    assert isinstance(other._dtype, (Integer, Bits))
    if areValues(self, other):
        return bitsArithOp__val(self, other, op)
    else:
        resT = self._dtype
        if self._dtype.signed is None:
            self = self._unsigned()

        if isinstance(other._dtype, Bits):
            assert other._dtype.bit_length() == resT.bit_length(
            ), (op, other._dtype.bit_length(), resT.bit_length())
            other = other._convSign(self._dtype.signed)
        elif isinstance(other._dtype, Integer):
            pass
        else:
            raise TypeError("%r %r %r" % (self, op, other))

        o = Operator.withRes(op, [self, other], self._dtype)
        return o._auto_cast(resT)


from hwt.hdl.value import Value, areValues
from hwt.hdl.types.defs import BOOL
from hwt.hdl.operator import Operator
from hwt.hdl.operatorDefs import AllOps
from hwt.doc_markers import internal

BoolVal = BOOL.getValueCls()

class HEnumVal(Value):

    @classmethod
    def fromPy(cls, val, typeObj, vldMask=None):
        """
        :param val: value of python type bool or None
        :param typeObj: instance of HEnum
        :param vldMask: if is None validity is resolved from val
            if is 0 value is invalidated
            if is 1 value has to be valid
        """
        if val is None:
            assert vldMask is None or vldMask == 0
            valid = False
            val = typeObj._allValues[0]
        else:
            if vldMask is None or vldMask == 1:
                assert isinstance(val, str)
                valid = True
            else:
                valid = False
                val = None

        return cls(val, typeObj, valid)

    @internal
    def _eq__val(self, other):
        eq = self.val == other.val \
            and self.vldMask == other.vldMask == 1

        vldMask = int(self.vldMask == other.vldMask == 1)
        updateTime = max(self.updateTime, other.updateTime)
        return BoolVal(eq, BOOL, vldMask, updateTime)

    def _eq(self, other):
        assert self._dtype is other._dtype

        if areValues(self, other):
            return self._eq__val(other)
        else:
            return Operator.withRes(AllOps.EQ, [self, other], BOOL)

    @internal
    def _ne__val(self, other):
        neq = self.val != other.val \
            and self.vldMask == other.vldMask == 1

        vldMask = int(self.vldMask == other.vldMask == 1)
        updateTime = max(self.updateTime, other.updateTime)
        return BoolVal(neq, BOOL, vldMask, updateTime)

    def __ne__(self, other):
        assert self._dtype is other._dtype

        if areValues(self, other):
            return self._ne__val(other)
        else:
            return Operator.withRes(AllOps.NEQ, [self, other], BOOL)


from functools import reduce
from operator import and_
from typing import List, Tuple, Dict

from hwt.hdl.sensitivityCtx import SensitivityCtx
from hwt.hdl.statementUtils import fill_stm_list_with_enclosure
from hwt.hdl.statements import HdlStatement, isSameHVal, isSameStatementList,\
    statementsAreSame, HwtSyntaxError
from hwt.hdl.types.enum import HEnum
from hwt.hdl.value import Value
from hwt.synthesizer.rtlLevel.mainBases import RtlSignalBase
from hwt.synthesizer.rtlLevel.rtlSignal import RtlSignal
from hwt.doc_markers import internal

class SwitchContainer(HdlStatement):
    """
    Structural container for switch statement for hdl rendering

    :ivar switchOn: select signal of switch
    :ivar cases: list of tuples (value, statements)
    :ivar default: list of statements (for branch "default")
    :ivar _case_value_index: dictionary {value:index} for every case in cases
    :ivar _case_enclosed_for: list of sets of enclosed signal for each case branch
    :ivar _default_enclosed_for: set of enclosed signals for branch default
    """

    def __init__(self, switchOn: RtlSignal,
                 cases: List[Tuple[Value, List[HdlStatement]]],
                 default: List[HdlStatement]=None,
                 parentStm: HdlStatement=None,
                 is_completly_event_dependent: bool=False):

        super(SwitchContainer, self).__init__(
            parentStm=parentStm,
            is_completly_event_dependent=is_completly_event_dependent)
        self.switchOn = switchOn
        self.cases = cases

        self.default = default

        self._case_value_index = {}
        for i, (v, _) in enumerate(cases):
            assert v not in self._case_value_index, v
            self._case_value_index[v] = i

        self._case_enclosed_for = None
        self._default_enclosed_for = None

    @internal
    def _clean_signal_meta(self):
        self._case_enclosed_for = None
        self._default_enclosed_for = None
        HdlStatement._clean_signal_meta(self)

    @internal
    def _collect_io(self):
        raise NotImplementedError()

    @internal
    def _discover_enclosure(self) -> None:
        assert self._enclosed_for is None
        enclosure = self._enclosed_for = set()
        case_enclosures = self._case_enclosed_for = []
        outputs = self._outputs

        for _, stms in self.cases:
            c_e = self._discover_enclosure_for_statements(stms, outputs)
            case_enclosures.append(c_e)

        self._default_enclosed_for = self._discover_enclosure_for_statements(
            self.default, outputs)

        t = self.switchOn._dtype
        if not self.default and len(self.cases) < t.domain_size():
            # cases does not cover all branches
            return

        for s in outputs:
            enclosed = True
            for e in case_enclosures:
                if s not in e:
                    enclosed = False
                    break

            if enclosed and (not self.default or s in self._default_enclosed_for):
                enclosure.add(s)

    @internal
    def _discover_sensitivity(self, seen) -> None:
        """
        Doc on parent class :meth:`HdlStatement._discover_sensitivity`
        """
        assert self._sensitivity is None, self
        ctx = self._sensitivity = SensitivityCtx()

        casual_sensitivity = set()
        self.switchOn._walk_sensitivity(casual_sensitivity, seen, ctx)
        if ctx.contains_ev_dependency:
            raise HwtSyntaxError(
                "Can not switch on event operator result", self.switchOn)
        ctx.extend(casual_sensitivity)

        for stm in self._iter_stms():
            stm._discover_sensitivity(seen)
            ctx.extend(stm._sensitivity)

    @internal
    def _fill_enclosure(self, enclosure: Dict[RtlSignalBase, HdlStatement]) -> None:
        """
        :attention: enclosure has to be discoverd first use _discover_enclosure()  method
        """
        select = []
        outputs = self._outputs
        for e in enclosure.keys():
            if e in outputs:
                select.append(e)

        for (_, stms), e in zip(self.cases, self._case_enclosed_for):
            fill_stm_list_with_enclosure(self, e, stms, select, enclosure)
            e.update(select)

        t = self.switchOn._dtype
        default_required = len(self.cases) < t.domain_size()

        if self.default is not None or default_required:
            self.default = fill_stm_list_with_enclosure(
                self, self._default_enclosed_for, self.default, select, enclosure)
            self._default_enclosed_for.update(select)

        self._enclosed_for.update(select)

    def _iter_stms(self):
        """
        Doc on parent class :meth:`HdlStatement._iter_stms`
        """
        for _, stms in self.cases:
            yield from stms

        if self.default is not None:
            yield from self.default

    @internal
    def _is_mergable(self, other) -> bool:
        """
        :return: True if other can be merged into this statement else False
        """
        if not isinstance(other, SwitchContainer):
            return False

        if not (self.switchOn is other.switchOn and
                len(self.cases) == len(other.cases) and
                self._is_mergable_statement_list(self.default, other.default)):
            return False

        for (vA, caseA), (vB, caseB) in zip(self.cases, other.cases):
            if vA != vB or not self._is_mergable_statement_list(caseA, caseB):
                return False

        return True

    @internal
    def _merge_with_other_stm(self, other: "IfContainer") -> None:
        """
        Merge other statement to this statement
        """
        merge = self._merge_statement_lists
        newCases = []
        for (c, caseA), (_, caseB) in zip(self.cases, other.cases):
            newCases.append((c, merge(caseA, caseB)))

        self.cases = newCases

        if self.default is not None:
            self.default = merge(self.default, other.default)

        self._on_merge(other)

    @internal
    def _try_reduce(self) -> Tuple[List["HdlStatement"], bool]:
        """
        Doc on parent class :meth:`HdlStatement._try_reduce`
        """
        io_change = False

        new_cases = []
        for val, statements in self.cases:
            _statements, rank_decrease, _io_change = self._try_reduce_list(
                statements)
            io_change |= _io_change
            self.rank -= rank_decrease
            new_cases.append((val, _statements))
        self.cases = new_cases

        if self.default is not None:
            self.default, rank_decrease, _io_change = self._try_reduce_list(
                self.default)
            self.rank -= rank_decrease
            io_change |= _io_change

        reduce_self = not self._condHasEffect()
        if reduce_self:
            if self.cases:
                res = self.cases[0][1]
            elif self.default is not None:
                res = self.default
            else:
                res = []
        else:
            res = [self, ]

        self._on_reduce(reduce_self, io_change, res)

        if not self.default:
            t = self.switchOn._dtype
            if isinstance(t, HEnum):
                dom_size = t.domain_size()
                val_cnt = len(t._allValues)
                if len(self.cases) == val_cnt and val_cnt < dom_size:
                    # bit representation is not fully matching enum description
                    # need to set last case as default to prevent latches
                    _, stms = self.cases.pop()
                    self.default = stms

        return res, io_change

    @internal
    def _condHasEffect(self) -> bool:
        """
        :return: True if statements in branches has different effect
        """
        if not self.cases:
            return False

        # [TODO]
        type_domain_covered = bool(self.default) or len(
            self.cases) == self.switchOn._dtype.domain_size()

        stmCnt = len(self.cases[0])
        if type_domain_covered and reduce(
                and_,
                [len(stm) == stmCnt
                 for _, stm in self.cases],
                True) and (self.default is None
                           or len(self.default) == stmCnt):
            stms = list(self._iter_stms())
            if statementsAreSame(stms):
                return False
            else:
                return True
        return True

    def isSame(self, other: HdlStatement) -> bool:
        """
        Doc on parent class :meth:`HdlStatement.isSame`
        """
        if self is other:
            return True

        if self.rank != other.rank:
            return False

        if isinstance(other, SwitchContainer) \
                and isSameHVal(self.switchOn, other.switchOn)\
                and len(self.cases) == len(other.cases)\
                and isSameStatementList(self.default, other.default):
            for (ac, astm), (bc, bstm) in zip(self.cases, other.cases):
                if not isSameHVal(ac, bc)\
                        or not isSameStatementList(astm, bstm):
                    return False
            return True
        return False


from hwt.doc_markers import internal
from hwt.hdl.operator import Operator
from hwt.hdl.operatorDefs import isEventDependentOp

@internal
def discoverEventDependency(sig):
    """
    :return: generator of tuples (event operator, signal)
    """

    try:
        drivers = sig.drivers
    except AttributeError:
        return

    if len(drivers) == 1:
        d = drivers[0]
        if isinstance(d, Operator):
            if isEventDependentOp(d.operator):
                yield (d.operator, d.operands[0])
            else:
                for op in d.operands:
                    yield from discoverEventDependency(op)


_indent = "    "
_indentCache = {}

def getIndent(indentNum):
    """
    Cached indent getter function
    """
    try:
        return _indentCache[indentNum]
    except KeyError:
        i = "".join([_indent for _ in range(indentNum)])
        _indentCache[indentNum] = i
        return i


from hwt.hdl.assignment import Assignment
from hwt.hdl.operator import Operator
from hwt.hdl.portItem import PortItem
from hwt.pyUtils.arrayQuery import arr_any
from hwt.serializer.generic.constants import SIGNAL_TYPE
from hwt.doc_markers import internal

@internal
def _isEventDependentDriver(d):
    return not isinstance(d, (PortItem, Operator)) \
        and d._now_is_event_dependent

@internal
def verilogTypeOfSig(signalItem):
    """
    Check if is register or wire
    """
    driver_cnt = len(signalItem.drivers)
    if signalItem._const or driver_cnt > 1 or\
       arr_any(signalItem.drivers, _isEventDependentDriver):
        return SIGNAL_TYPE.REG
    else:
        if driver_cnt == 1:
            d = signalItem.drivers[0]
            if not isinstance(d, (Assignment, PortItem)):
                return SIGNAL_TYPE.REG

        return SIGNAL_TYPE.WIRE


import math
from typing import List, Tuple, Union

from hwt.doc_markers import internal
from hwt.hdl.typeShortcuts import hInt
from hwt.hdl.types.bits import Bits
from hwt.hdl.types.defs import BOOL, STR, BIT
from hwt.hdl.types.hdlType import HdlType
from hwt.hdl.types.integer import Integer
from hwt.serializer.vhdl.serializer import VhdlSerializer
from hwt.synthesizer.dummyPlatform import DummyPlatform
from hwt.synthesizer.interface import Interface
from hwt.synthesizer.interfaceLevel.unitImplHelpers import getSignalName
from hwt.synthesizer.param import evalParam, Param
from hwt.synthesizer.rtlLevel.mainBases import RtlSignalBase
from hwt.synthesizer.rtlLevel.rtlSignal import RtlSignal
from hwt.synthesizer.unit import Unit
from hwt.synthesizer.utils import toRtl
from ipCorePackager.otherXmlObjs import Value
from ipCorePackager.packager import IpCorePackager

class VivadoTclExpressionSerializer(VhdlSerializer):

    # disabled because this code is not reachable in current implemetation
    @staticmethod
    def SignalItem(si, declaration=False):
        raise NotImplementedError(si)

class IpPackager(IpCorePackager):
    """
    IP-core packager

    :summary: Packs HDL, constraint and other files to IP-Core package
        for distribution and simple integration

    """

    def __init__(self, topUnit: Unit, name: str=None,
                 extraVhdlFiles: List[str]=[],
                 extraVerilogFiles: List[str]=[],
                 serializer=VhdlSerializer,
                 targetPlatform=DummyPlatform()):
        """
        :param topObj: Unit instance of top component
        :param name: optional name of top
        :param extraVhdlFiles: list of extra vhdl file names for files
            which should be distributed in this IP-core
        :param extraVerilogFiles: same as extraVhdlFiles just for Verilog
        :param serializer: serializer which specifies target HDL language
        :param targetPlatform: specifies properties of target platform, like available resources, vendor, etc.
        """
        assert not topUnit._wasSynthetised()
        if not name:
            name = topUnit._getDefaultName()

        super(IpPackager, self).__init__(
            topUnit, name, extraVhdlFiles, extraVerilogFiles)
        self.serializer = serializer
        self.targetPlatform = targetPlatform

    @internal
    def toHdlConversion(self, top, topName: str, saveTo: str) -> List[str]:
        """
        :param top: object which is represenation of design
        :param topName: name which should be used for ipcore
        :param saveTo: path of directory where generated files should be stored

        :return: list of file namens in correct compile order
        """

        return toRtl(top,
                     saveTo=saveTo,
                     name=topName,
                     serializer=self.serializer,
                     targetPlatform=self.targetPlatform)

    @internal
    def paramToIpValue(self, idPrefix: str, g: Param, resolve) -> Value:
        val = Value()
        val.id = idPrefix + g.name
        val.resolve = resolve
        t = g._dtype

        def getVal():
            v = evalParam(g.defVal)
            if v.vldMask:
                return v.val
            else:
                return 0

        def bitString(w):
            val.format = "bitString"
            digits = math.ceil(w / 4)
            val.text = ('0x%0' + str(digits) + 'X') % getVal()
            val.bitStringLength = str(w)

        if t == BOOL:
            val.format = "bool"
            val.text = str(bool(getVal())).lower()
        elif isinstance(t, Integer):
            val.format = "long"
            val.text = str(getVal())
        elif t == STR:
            val.format = "string"
            val.text = g.defVal.staticEval().val
        elif isinstance(t, Bits):
            bitString(g.defVal._dtype.bit_length())
        else:
            raise NotImplementedError(
                "Not implemented for datatype %s" % repr(t))
        return val

    @internal
    def getParamPhysicalName(self, p: Param):
        return p.name

    @internal
    def getParamType(self, p: Param) -> HdlType:
        return p._dtype

    @internal
    def iterParams(self, unit: Unit):
        return unit._entity.generics

    @internal
    def iterInterfaces(self, top: Unit):
        return top._interfaces

    @internal
    def serializeType(self, hdlType: HdlType) -> str:
        """
        :see: doc of method on parent class
        """

        def createTmpVar(suggestedName, dtype):
            raise NotImplementedError(
                "Can not seraialize hdl type %r into"
                "ipcore format" % (hdlType))

        return VhdlSerializer.HdlType(hdlType, VhdlSerializer.getBaseContext())

    @internal
    def getVectorFromType(self, dtype) -> Union[bool, None, Tuple[int, int]]:
        """
        :see: doc of method on parent class
        """
        if dtype == BIT:
            return False
        elif isinstance(dtype, Bits):
            return [evalParam(dtype.width) - 1, hInt(0)]

    @internal
    def getInterfaceType(self, intf: Interface) -> HdlType:
        """
        :see: doc of method on parent class
        """
        return intf._dtype

    @internal
    def getInterfaceLogicalName(self, intf: Interface):
        """
        :see: doc of method on parent class
        """
        return getSignalName(intf)

    @internal
    def getInterfacePhysicalName(self, intf: Interface):
        """
        :see: doc of method on parent class
        """
        return intf._sigInside.name

    @internal
    def getInterfaceDirection(self, thisIntf):
        """
        :see: doc of method on parent class
        """
        return thisIntf._direction

    @internal
    def getExprVal(self, val, do_eval=False):
        """
        :see: doc of method on parent class
        """
        ctx = VhdlSerializer.getBaseContext()

        def createTmpVar(suggestedName, dtype):
            raise NotImplementedError(
                "Width value can not be converted do ipcore format (%r)",
                val)

        ctx.createTmpVarFn = createTmpVar
        if do_eval:
            val = val.staticEval()
        val = VivadoTclExpressionSerializer.asHdl(val, ctx)
        return val

    @internal
    def getTypeWidth(self, dtype: HdlType, do_eval=False) -> Tuple[int, Union[int, RtlSignal], bool]:
        """
        :see: doc of method on parent class
        """
        width = dtype.width
        if isinstance(width, int):
            widthStr = str(width)
        else:
            widthStr = self.getExprVal(width, do_eval=do_eval)

        return width, widthStr, False

    @internal
    def getObjDebugName(self, obj: Union[Interface, Unit, Param]) -> str:
        """
        :see: doc of method on parent class
        """
        return obj._getFullName()

    @internal
    def serialzeValueToTCL(self, val, do_eval=False) -> Tuple[str, str, bool]:
        """
        :see: doc of method on parent class
        """
        if isinstance(val, int):
            val = hInt(val)
        if do_eval:
            val = val.staticEval()

        if isinstance(val, RtlSignalBase):
            ctx = VivadoTclExpressionSerializer.getBaseContext()
            tclVal = VivadoTclExpressionSerializer.asHdl(val, ctx)
            tclValVal = VivadoTclExpressionSerializer.asHdl(
                        val.staticEval())
            return tclVal, tclValVal, False
        else:

            tclVal = VivadoTclExpressionSerializer.asHdl(val, None)
            return tclVal, tclVal, True


from types import MethodType

from hwt.synthesizer.exceptions import IntfLvlConfErr
from hwt.synthesizer.interfaceLevel.mainBases import UnitBase, InterfaceBase
from hwt.synthesizer.param import Param
from hwt.synthesizer.hObjList import HObjList
from hwt.doc_markers import internal

@internal
def nameAvailabilityCheck(obj, propName, prop):
    """
    Check if not redefining property on obj
    """
    if getattr(obj, propName, None) is not None:
        raise IntfLvlConfErr("%r already has property %s old:%s new:%s" %
                             (obj, propName, repr(getattr(obj, propName)), prop))

@internal
class MakeParamsShared(object):
    """
    All newly added interfaces and units will share all parametes with unit
    specified in constructor of this object.
    """

    def __init__(self, unit, exclude, prefix):
        self.unit = unit
        self.exclude = exclude
        self.prefix = prefix

    def __enter__(self):
        orig = self.unit._setAttrListener
        self.orig = orig
        exclude = self.exclude
        prefix = self.prefix

        def MakeParamsSharedWrap(self, iName, i):
            if isinstance(i, (InterfaceBase, UnitBase, HObjList)):
                i._updateParamsFrom(self, exclude=exclude, prefix=prefix)
            return orig(iName, i)

        self.unit._setAttrListener = MethodType(MakeParamsSharedWrap,
                                                self.unit)
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type is None:
            self.unit._setAttrListener = self.orig

@internal
class MakeClkRstAssociations(object):
    """
    All newly added interfaces will be associated with clk, rst
    specified in constructor of this object.
    """

    def __init__(self, unit, clk=None, rst=None):
        self.unit = unit
        self.clk = clk
        self.rst = rst

    def __enter__(self):
        orig = self.unit._setAttrListener
        self.orig = orig
        clk = self.clk
        rst = self.rst

        def MakeClkRstAssociationsWrap(self, iName, i):
            if isinstance(i, (InterfaceBase, HObjList)):
                i._make_association(clk=clk, rst=rst)
            return orig(iName, i)

        self.unit._setAttrListener = MethodType(MakeClkRstAssociationsWrap,
                                                self.unit)
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type is None:
            self.unit._setAttrListener = self.orig

class PropDeclrCollector(object):
    """
    Collect properties of this object to containers by specified listeners
    """

    def _config(self) -> None:
        """
        Configure object parameters

        * setup all parameters on this object,
          use Param class instances to allow use of parameter inheritance
        * called in __init__ of class
        """
        pass

    def _declr(self) -> None:
        """
        declarations

        * do all declarations of externally accessible objects there (Interfaces)
        * _declr method is called after _config
        * if this object is Unit all interfaces are threaten as externally accessible interfaces
          if this object is Interface all subinterfaces are loaded
        """
        pass

    def _impl(self) -> None:
        """
        implementations

        * implement functionality of design there
        * called after _declr
        """
        pass

    @internal
    def __setattr__(self, attr, value) -> None:
        """setattr with listener injector"""
        try:
            saListerner = self._setAttrListener
        except AttributeError:
            super().__setattr__(attr, value)
            return

        if saListerner:
            saListerner(attr, value)
        super().__setattr__(attr, value)

    # configuration phase
    @internal
    def _loadConfig(self) -> None:
        if not hasattr(self, '_params'):
            self._params = []

        self._setAttrListener = self._paramCollector
        self._config()
        self._setAttrListener = None

    @internal
    def _registerParameter(self, pName, parameter) -> None:
        """
        Register Param object on interface level object
        """
        nameAvailabilityCheck(self, pName, parameter)
        # resolve name in this scope
        try:
            hasName = parameter._name is not None
        except AttributeError:
            hasName = False
        if not hasName:
            parameter._name = pName
        # add name in this scope
        parameter._registerScope(pName, self)

        if parameter.hasGenericName:
            parameter.name = pName

        if parameter._parent is None:
            parameter._parent = self

        self._params.append(parameter)

    def _paramsShared(self, exclude=None, prefix="") -> MakeParamsShared:
        """
        Auto-propagate params by name to child components and interfaces
        Usage:

        .. code-block:: python

            with self._paramsShared():
                # your interfaces and unit which should share all params with "self" there

        :param exclude: params which should not be shared
        :param prefix: prefix which should be added to name of child parameters
            before parameter name matching
        """
        return MakeParamsShared(self, exclude=exclude, prefix=prefix)

    def _make_association(self, clk=None, rst=None) -> None:
        """
        Associate this object with specified clk/rst
        """
        if clk is not None:
            assert self._associatedClk is None
            self._associatedClk = clk

        if rst is not None:
            assert self._associatedRst is None
            self._associatedRst = rst

    def _associated(self, clk=None, rst=None) -> MakeClkRstAssociations:
        """
        associate newly added interfaces to "self" with selected clk, rst
        (if interface is not associated agents try to find clk/rst by _getAssociatedClk/_getAssociatedRst
        which will search for any clk/rst on parent recursively)
        Usage:

        .. code-block:: python

            with self._associated(clk=self.myClk, rst=self.myRst):
                self.myAxi = AxiStrem()
                # this interface is associated with myClk and myRst
                # simulation agents and component builders will use them

        :param exclude: params which should not be shared
        """
        return MakeClkRstAssociations(self, clk, rst)

    def _updateParamsFrom(self, otherObj:"PropDeclrCollector", updater, exclude:set, prefix:str) -> None:
        """
        Update all parameters which are defined on self from otherObj

        :param otherObj: other object which Param instances should be updated
        :param updater: updater function(self, myParameter, onOtherParameterName, otherParameter)
        :param exclude: iterable of parameter on otherObj object which should be excluded
        :param prefix: prefix which should be added to name of paramters of this object before matching
            parameter name on parent
        """
        excluded = set()
        if exclude is not None:
            exclude = set(exclude)

        for myP in self._params:
            pPName = prefix + myP._scopes[self][1]
            try:
                otherP = getattr(otherObj, pPName)
                if not isinstance(otherP, Param):
                    continue
            except AttributeError:
                continue

            if exclude and otherP in exclude:
                excluded.add(otherP)
                continue
            updater(self, myP, otherP)

        if exclude is not None:
            # assert that what should be excluded really exists
            assert excluded == exclude

    # declaration phase
    @internal
    def _registerUnit(self, uName, unit):
        """
        Register unit object on interface level object
        """
        nameAvailabilityCheck(self, uName, unit)
        assert unit._parent is None
        unit._parent = self
        unit._name = uName
        self._units.append(unit)

    @internal
    def _registerInterface(self, iName, intf, isPrivate=False):
        """
        Register interface object on interface level object
        """
        nameAvailabilityCheck(self, iName, intf)
        assert intf._parent is None
        intf._parent = self
        intf._name = iName
        intf._ctx = self._ctx

        if isPrivate:
            self._private_interfaces.append(intf)
            intf._isExtern = False
        else:
            self._interfaces.append(intf)
            intf._isExtern = True

    @internal
    def _declrCollector(self, name, prop):
        if name in ["_associatedClk", "_associatedRst"]:
            object.__setattr__(self, name, prop)
            return

        if isinstance(prop, InterfaceBase):
            self._registerInterface(name, prop)
        elif isinstance(prop, UnitBase):
            self._registerUnit(name, prop)
        elif isinstance(prop, HObjList):
            self._registerArray(name, prop)

    @internal
    def _registerArray(self, name, items):
        """
        Register array of items on interface level object
        """
        items._parent = self
        items._name = name
        for i, item in enumerate(items):
            setattr(self, "%s_%d" % (name, i), item)

    # implementation phase
    @internal
    def _loadMyImplementations(self):
        self._setAttrListener = self._implCollector
        self._impl()
        self._setAttrListener = None

    @internal
    def _registerUnitInImpl(self, uName, u):
        """
        :attention: unit has to be parametrized before it is registered
            (some components can change interface by parametrization)
        """
        self._registerUnit(uName, u)
        u._loadDeclarations()
        self._lazyLoaded.extend(u._toRtl(self._targetPlatform))
        u._signalsForMyEntity(self._ctx, "sig_" + uName)

    @internal
    def _registerIntfInImpl(self, iName, i):
        """
        Register interface in implementation phase
        """
        raise NotImplementedError()

    @internal
    def _paramCollector(self, pName, prop):
        if isinstance(prop, Param):
            self._registerParameter(pName, prop)

    @internal
    def _implCollector(self, name, prop):
        if isinstance(prop, InterfaceBase):
            self._registerIntfInImpl(name, prop)
        elif isinstance(prop, UnitBase):
            self._registerUnitInImpl(name, prop)


from typing import Generator, Union

from hwt.doc_markers import internal
from hwt.hdl.sensitivityCtx import SensitivityCtx
from hwt.hdl.statements import HdlStatement
from hwt.hdl.types.hdlType import HdlType
from hwt.hdl.value import Value
from hwt.hdl.variables import SignalItem
from hwt.pyUtils.uniqList import UniqList
from hwt.simulator.exceptions import SimException
from hwt.synthesizer.rtlLevel.mainBases import RtlSignalBase
from hwt.synthesizer.rtlLevel.signalUtils.exceptions import MultipleDriversErr,\
    NoDriverErr
from hwt.synthesizer.rtlLevel.signalUtils.ops import RtlSignalOps

class RtlSignal(RtlSignalBase, SignalItem, RtlSignalOps):
    """
    RtlSignal signal is container of connection
    between statements and operators

    :ivar _usedOps: dictionary of used operators which can be reused
    :ivar endpoints: UniqList of operators and statements
        for which this signal is driver.
    :ivar drivers: UniqList of operators and statements
        which can drive this signal.
    :ivar hiden: means that this signal is part of expression
        and should not be rendered
    :ivar processCrossing: means that this signal is crossing process boundary

    :cvar __instCntr: counter used for generating instance ids
    :ivar _instId: internally used only for intuitive sorting of statements
        in serialized code
    """
    __instCntr = 0

    def __init__(self, ctx, name, dtype, defVal=None, nopVal=None,
                 useNopVal=False, virtualOnly=False):
        """
        :param ctx: context - RtlNetlist which is this signal part of
        :param name: name hint for this signal, if is None name
            is chosen automatically
        :param defVal: value which is used for reset and as default value
            in hdl
        :param useNopVal: use nopVal or ignore it
        :param nopVal: value which is used to fill up statements when no other
            value is assigned
        """

        if name is None:
            name = "sig_"
            self.hasGenericName = True
        else:
            self.hasGenericName = False

        assert isinstance(dtype, HdlType)
        super(RtlSignal, self).__init__(name, dtype, defVal, virtualOnly=virtualOnly)
        self.ctx = ctx

        if ctx:
            # params does not have any context on created
            # and it is assigned after param is bounded to unit or interface
            ctx.signals.add(self)

        # set can not be used because hash of items are changing
        self.endpoints = UniqList()
        self.drivers = UniqList()
        self._usedOps = {}
        self.hidden = True
        self._instId = RtlSignal._nextInstId()

        self._nopVal = nopVal
        self._useNopVal = useNopVal
        self._const = False

    @internal
    @classmethod
    def _nextInstId(cls):
        """
        Get next instance id
        """
        i = cls.__instCntr
        cls.__instCntr += 1
        return i

    def staticEval(self):
        # operator writes in self._val new value
        if self.drivers:
            for d in self.drivers:
                d.staticEval()
        else:
            if isinstance(self.defVal, RtlSignal):
                self._val = self.defVal._val.staticEval()
            else:
                if self._val.updateTime < 0:
                    # _val is invalid initialization value
                    self._val = self.defVal.clone()

        if not isinstance(self._val, Value):
            raise SimException(
                "Evaluation of signal returned not supported object (%r)"
                % (self._val, ))

        return self._val

    def singleDriver(self):
        """
        Returns a first driver if signal has only one driver.
        """
        # [TODO] no driver exception
        drv_cnt = len(self.drivers)
        if not drv_cnt:
            raise NoDriverErr(self)
        elif drv_cnt != 1:
            raise MultipleDriversErr(self)

        return self.drivers[0]

    @internal
    def _walk_sensitivity(self, casualSensitivity: set, seen: set, ctx: SensitivityCtx)\
            -> Generator[Union["RtlSignal", "Operator"], None, None]:
        seen.add(self)

        if self._const:
            return

        if not self.hidden:
            casualSensitivity.add(self)
            return

        try:
            op = self.singleDriver()
        except (MultipleDriversErr, NoDriverErr):
            op = None

        if op is None or isinstance(op, HdlStatement):
            casualSensitivity.add(self)
            return

        op._walk_sensitivity(casualSensitivity, seen, ctx)

    @internal
    def _walk_public_drivers(self, seen: set) -> Generator["RtlSignal", None, None]:
        seen.add(self)
        if not self.hidden:
            yield self
            return

        assert self.drivers, self
        for d in self.drivers:
            yield from d._walk_public_drivers(seen)


from typing import Generator, Union

from hwt.hdl.hdlObject import HdlObject
from hwt.hdl.operatorDefs import isEventDependentOp
from hwt.hdl.sensitivityCtx import SensitivityCtx
from hwt.hdl.value import Value
from hwt.pyUtils.arrayQuery import arr_all
from hwt.synthesizer.rtlLevel.rtlSignal import RtlSignal, RtlSignalBase
from hwt.doc_markers import internal

@internal
def getCtxFromOps(operands):
    for o in operands:
        if isinstance(o, RtlSignalBase):
            return o.ctx
    raise TypeError("Can not find context because there is no signal in ops"
                    "(value operators should be already resolved)")

def isConst(item):
    """
    :return: True if expression is constant
    """
    return isinstance(item, Value) or item._const

class Operator(HdlObject):
    """
    Class of operator in expression tree

    :ivar operands: list of operands
    :ivar evalFn: function to evaluate this operator
    :ivar operator: OpDefinition instance
    :ivar result: result signal of this operator
    """

    def __init__(self, operator, operands):
        self.operands = tuple(operands)
        self.operator = operator
        self.result = None

    @internal
    def registerSignals(self, outputs=[]):
        """
        Register potential signals to drivers/endpoints
        """
        for o in self.operands:
            if isinstance(o, RtlSignalBase):
                if o in outputs:
                    o.drivers.append(self)
                else:
                    o.endpoints.append(self)
            elif isinstance(o, Value):
                pass
            else:
                raise NotImplementedError(
                    "Operator operands can be"
                    " only signal or values got:%r" % (o))

    @internal
    def staticEval(self):
        """
        Recursively statistically evaluate result of this operator
        """
        for o in self.operands:
            o.staticEval()
        self.result._val = self.evalFn()

    @internal
    def evalFn(self, simulator=None):
        """
        Syntax sugar
        """
        return self.operator.eval(self, simulator=simulator)

    @internal
    def _walk_sensitivity(self, casualSensitivity: set, seen: set, ctx: SensitivityCtx)\
        -> Generator[Union[RtlSignalBase, "Operator"],
                     None, None]:
        seen.add(self)

        if isEventDependentOp(self.operator):
            if ctx.contains_ev_dependency:
                assert self in ctx, "has to have only one clock one clock"
            ctx.contains_ev_dependency = True
            ctx.append(self)
        else:
            # walk source of signal
            for operand in self.operands:
                if operand not in seen:
                    operand._walk_sensitivity(casualSensitivity, seen, ctx)

    @internal
    def _walk_public_drivers(self, seen: set) -> Generator["RtlSignal", None, None]:
        for op in self.operands:
            if not isinstance(op, Value) and op not in seen:
                yield from op._walk_public_drivers(seen)

    @internal
    def __eq__(self, other):
        return self is other or (
            type(self) is type(other) and
            self.operator == other.operator and
            self.operands == other.operands
        )

    @internal
    @staticmethod
    def withRes(opDef, operands, resT, outputs=[]):
        """
        Create operator with result signal

        :ivar resT: data type of result signal
        :ivar outputs: iterable of singnals which are outputs
            from this operator
        """
        op = Operator(opDef, operands)
        out = RtlSignal(getCtxFromOps(operands), None, resT)
        out._const = arr_all(op.operands, isConst)
        out.drivers.append(op)
        out.origin = op
        op.result = out
        op.registerSignals(outputs)
        if out._const:
            out.staticEval()
        return out

    @internal
    def __hash__(self):
        return hash((self.operator, self.operands))


from copy import copy

class SerializerCtx():
    """
    Serializer context

    :ivar scope: instance of NameScope used to check id availability
    :ivar indent: number of visual indentations for code in this context
    :ivar createTmpVarFn: function (sugestedName, dtype) returns variable
        this function will be called to create tmp variables
    :ivar constCache: constant cache to extract frequently used large constant
        values from code (visual improvement)
    :ivar currentUnit: current Unit instance or None
    """

    def __init__(self,  scope, indent: int, createTmpVarFn, constCache=None):
        self.scope = scope
        self.indent = indent
        self.currentUnit = None

        if createTmpVarFn is None:
            self.createTmpVarFn = self.defaultCreateTmpVarFn
        else:
            self.createTmpVarFn = createTmpVarFn

        self.constCache = constCache

    def defaultCreateTmpVarFn(self, sugestedName, dtype):
        raise NotImplementedError()

    def withIndent(self, indent=1):
        """
        Create copy of this context with increased indent
        """
        ctx = copy(self)
        ctx.indent += indent
        return ctx


from hwt.code import connect
from hwt.interfaces.std import Clk, Rst_n, Rst

def addClkRstn(obj):
    """
    Construct clk, rst_n signal on object (usually Unit/Interface instance)
    """
    obj.clk = Clk()
    obj.rst_n = Rst_n()

def addClkRst(obj):
    """
    Construct clk, rst signal on object (usually Unit/Interface instance)
    """
    obj.clk = Clk()
    obj.rst = Rst()

def _tryConnect(src, unit, intfName):
    """
    Try connect src to interface of specified name on unit.
    Ignore if interface is not present or if it already has driver.
    """
    try:
        dst = getattr(unit, intfName)
    except AttributeError:
        return
    if not dst._sig.drivers:
        connect(src, dst)

def propagateClk(obj):
    """
    Propagate "clk" clock signal to all subcomponents
    """
    clk = obj.clk
    for u in obj._units:
        _tryConnect(clk, u, 'clk')

def propagateClkRstn(obj):
    """
    Propagate "clk" clock and negative reset "rst_n" signal
    to all subcomponents
    """
    clk = obj.clk
    rst_n = obj.rst_n

    for u in obj._units:
        _tryConnect(clk, u, 'clk')
        _tryConnect(rst_n, u, 'rst_n')
        _tryConnect(~rst_n, u, 'rst')

def propagateClkRst(obj):
    """
    Propagate "clk" clock and reset "rst" signal to all subcomponents
    """
    clk = obj.clk
    rst = obj.rst

    for u in obj._units:
        _tryConnect(clk, u, 'clk')
        _tryConnect(~rst, u, 'rst_n')
        _tryConnect(rst, u, 'rst')

def propagateRstn(obj):
    """
    Propagate negative reset "rst_n" signal
    to all subcomponents
    """
    rst_n = obj.rst_n

    for u in obj._units:
        _tryConnect(rst_n, u, 'rst_n')
        _tryConnect(~rst_n, u, 'rst')

def propagateRst(obj):
    """
    Propagate reset "rst" signal
    to all subcomponents
    """
    rst = obj.rst

    for u in obj._units:
        _tryConnect(~rst, u, 'rst_n')
        _tryConnect(rst, u, 'rst')