def _runSeqProcesses(self) -> Generator[None, None, None]: """ Delta step for event dependent processes """ updates = [] for proc in self._seqProcsToRun: try: outContainer = self._outputContainers[proc] except KeyError: # processes does not have to have outputs outContainer = None proc(self, outContainer) if outContainer is not None: updates.append(outContainer) self._seqProcsToRun = UniqList() self._runSeqProcessesPlaned = False for cont in updates: for sigName, sig in cont._all_signals: newVal = getattr(cont, sigName) if newVal is not None: v = self._conflictResolveStrategy(newVal) updater, _ = v sig.simUpdateVal(self, updater) setattr(cont, sigName, None) return yield
class SaveToFilesFlat(StoreManager): """ Store all produced code to a single directory, file per component. """ def __init__(self, serializer_cls: DummySerializerConfig, root: str, _filter: "SerializerFilter"=None, name_scope: Optional[NameScope]=None): super(SaveToFilesFlat, self).__init__( serializer_cls, _filter=_filter, name_scope=name_scope) self.root = root self.files = UniqList() self.constraints_file_spoted = False os.makedirs(root, exist_ok=True) def write(self, obj: Union[iHdlObj, HdlConstraintList]): if isinstance(obj, HdlConstraintList): fName = "constraints" + self.serializer_cls.TO_CONSTRAINTS.fileExtension else: fName = obj.name + self.serializer_cls.fileExtension fp = os.path.join(self.root, fName) self.files.append(fp) if self.constraints_file_spoted: m = 'a' else: m = 'w' self.constraints_file_spoted = True with open(fp, m) as f: s = SaveToStream(self.serializer_cls, f, self.filter, self.name_scope) s.write(obj)
def toElkJson(self, idStore: "ElkIdStore", path_prefix: ComponentPath=ComponentPath(), isTop=True): props = { "org.eclipse.elk.portConstraints": self.portConstraints.name, 'org.eclipse.elk.layered.mergeEdges': 1, } hw_meta = { "name": self.name, "cls": self.cls, } d = { "hwMeta": hw_meta, "properties": props } hideChildren = False if self.bodyText is not None: hw_meta["bodyText"] = self.bodyText if isTop: self.toElkJson_registerNodes(idStore, path_prefix) self.toElkJson_registerPorts(idStore, path_prefix) else: d["id"] = str(idStore[path_prefix / self]) hideChildren = True # if self.parent.parent is not None: # props["org.eclipse.elk.noLayout"] = True d["ports"] = [p.toElkJson(idStore, path_prefix) for p in self.iterPorts()] children, path_prefix = self._getUniqRefChildren(path_prefix) if children: assert isinstance(children, list) formalParent = self._shared_component_with if formalParent is None: formalParent = self nodes = [] edges = UniqList() for ch in children: for e in ch.iterEdges(): if e.parentNode is formalParent: edges.append(e) for ch in children: nodes.append(ch.toElkJson(idStore, isTop=False, path_prefix=path_prefix)) nodes.sort(key=lambda n: n["id"]) d["_children" if hideChildren else "children"] = nodes for e in edges: idStore.registerEdge(path_prefix / e) d["_edges" if hideChildren else "edges"] = [e.toElkJson(idStore, path_prefix) for e in edges] hw_meta["maxId"] = idStore.getMaxId() return d
def __init__(self, parentStm=None, sensitivity=None, is_completly_event_dependent=False): self._is_completly_event_dependent = is_completly_event_dependent self._now_is_event_dependent = is_completly_event_dependent self.parentStm = parentStm self._inputs = UniqList() self._outputs = UniqList() self._enclosed_for = None self._sensitivity = sensitivity self.rank = 0
def __init__(self, serializer_cls: DummySerializerConfig, root: str, _filter: "SerializerFilter"=None, name_scope: Optional[NameScope]=None): super(SaveToFilesFlat, self).__init__( serializer_cls, _filter=_filter, name_scope=name_scope) self.root = root self.files = UniqList() self.module_path_prefix = None os.makedirs(root, exist_ok=True)
def __init__(self, parentStm: Optional["HdlStatement"] = None, sensitivity: Optional[UniqList] = None, event_dependent_from_branch: Optional[int] = None): assert event_dependent_from_branch is None or isinstance( event_dependent_from_branch, int), event_dependent_from_branch self._event_dependent_from_branch = event_dependent_from_branch self.parentStm = parentStm self._inputs = UniqList() self._outputs = UniqList() self._enclosed_for = None self._sensitivity = sensitivity self.rank = 0
def __init__(self, others: NetCtxs, actualKey, seqNo: int): """ :param seqNo: sequential number used to sustain determinism while iterating over instances of this class in dictionary """ self.parentNode = others.parentNode assert isinstance(self.parentNode, LNode), self.parentNode self.actualKeys = [ actualKey, ] self.others = others self.drivers = UniqList() self.endpoints = UniqList() self.seqNo = seqNo
def __init__(self, ctx, name, dtype, def_val=None, nop_val=NO_NOPVAL, virtual_only=False, is_const=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 def_val: value which is used for reset and as default value in hdl :param nop_val: value which is used to fill up statements when no other value is assigned, use NO_NOPVAL to dissable :param is_const: flag which tell that this signal can not have any other driver than a default value """ if name is None: name = "sig_" self.hasGenericName = True else: self.hasGenericName = False assert isinstance(dtype, HdlType) super(RtlSignal, self).__init__(name, dtype, def_val, virtual_only=virtual_only) 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._usedOpsAlias = {} self.hidden = True self._instId = RtlSignal._nextInstId() self._nop_val = nop_val self._const = is_const self.origin = None
def _runCombProcesses(self) -> None: """ Delta step for combinational processes """ for proc in self._combProcsToRun: cont = self._outputContainers[proc] proc(self, cont) for sigName, sig in cont._all_signals: newVal = getattr(cont, sigName) if newVal is not None: res = self._conflictResolveStrategy(newVal) # prepare update updater, isEvDependent = res self._valuesToApply.append( (sig, updater, isEvDependent, proc)) setattr(cont, sigName, None) # else value is latched self._combProcsToRun = UniqList()
def _on_reduce(self, self_reduced: bool, io_changed: bool, result_statements: List["HdlStatement"]) -> None: """ Update signal IO after reduce attempt :param self_reduced: if True this object was reduced :param io_changed: if True IO of this object may changed and has to be updated :param result_statements: list of statements which are result of reduce operation on this statement """ parentStm = self.parentStm if self_reduced: was_top = parentStm is None # update signal drivers/endpoints if was_top: # disconnect self from signals ctx = self._get_rtl_context() ctx.statements.remove(self) ctx.statements.update(result_statements) for i in self._inputs: i.endpoints.discard(self) for o in self._outputs: o.drivers.remove(self) for stm in result_statements: stm.parentStm = parentStm if parentStm is None: # connect signals to child statements for inp in stm._inputs: inp.endpoints.append(stm) for outp in stm._outputs: outp.drivers.append(stm) else: # parent has to update it's inputs/outputs if io_changed: self._inputs = UniqList() self._outputs = UniqList() self._collect_io()
def __init__(self, config=None): super(HdlSimulator, self).__init__() if config is None: # default config config = HdlSimConfig() self.config = config self.now = 0.0 self._combUpdateDonePlaned = False self._applyValPlaned = False self._runSeqProcessesPlaned = False # (signal, value) tuples which should be applied before # new round of processes # will be executed self._valuesToApply = [] self._seqProcsToRun = UniqList() self._combProcsToRun = UniqList() # container of outputs for every process self._outputContainers = {} self._events = SimCalendar()
def unhideResultsOfIndexingAndConcatOnPublicSignals(netlist: RtlNetlist): openset = UniqList( sorted((s for s in netlist.signals if not s.hidden), key=RtlSignal_sort_key)) epsToReplace = [] while openset: s = openset.pop() s: RtlSignal for ep in s.endpoints: # search for index ops if isinstance(ep, Operator)\ and ep.operator == AllOps.INDEX\ and ep.operands[0] is s: ep: Operator isIndexInBramWrite = isinstance(s._dtype, HArray)\ and arr_all(ep.result.endpoints, lambda ep: isinstance(ep, HdlStatement)\ and ep._event_dependent_from_branch == 0) if not isIndexInBramWrite and ep.result.hidden: epsToReplace.append(ep) for ep in epsToReplace: ep: Operator r = ep.result assert len(r.drivers) == 1, r r.hidden = False i = ep.operands[1] ep._destroy() # instantiate new hidden signal for result of index new_r = s[i] assert new_r is not r, r # and instantiate HdlAssignmentContainer to this new signal from the # old one r(new_r) openset.append(r) epsToReplace.clear()
def __init__(self, topUnit: Unit, name: str = None, extraVhdlFiles: List[str] = [], extraVerilogFiles: List[str] = [], serializer=VhdlSerializer, targetPlatform=DummyPlatform()): assert not topUnit._wasSynthetised() self.topUnit = topUnit self.serializer = serializer self.targetPlatform = targetPlatform if name: self.name = name else: self.name = self.topUnit._getDefaultName() self.hdlFiles = UniqList() for f in extraVhdlFiles: self.hdlFiles.append(f) for f in extraVerilogFiles: self.hdlFiles.append(f)
class NetCtx(): def __init__(self, others: NetCtxs, actualKey, seqNo: int): """ :param seqNo: sequential number used to sustain determinism while iterating over instances of this class in dictionary """ self.parentNode = others.parentNode assert isinstance(self.parentNode, LNode), self.parentNode self.actualKeys = [ actualKey, ] self.others = others self.drivers = UniqList() self.endpoints = UniqList() self.seqNo = seqNo def extend(self, other: "NetCtx"): self.drivers.extend(other.drivers) self.endpoints.extend(other.endpoints) def addDriver(self, src: Union[RtlSignalBase, LPort]): if isinstance(src, RtlSignalBase): return self.others.joinNetsByKeyVal(src, self) else: if self.parentNode is src.parentNode: # connection between input and output on nodes with same parent assert src.direction == PortType.INPUT, src elif self.parentNode.parent is src.parentNode: # source is parent input port assert src.direction == PortType.INPUT, src else: # source is child output port assert self.parentNode is src.parentNode.parent, src assert src.direction == PortType.OUTPUT, src return self.drivers.append(src) def addEndpoint(self, dst: Union[RtlSignalBase, LPort]): if isinstance(dst, RtlSignalBase): return self.others.joinNetsByValKey(self, dst) else: if self.parentNode is dst.parentNode: # connection between input and output on nodes with same parent assert dst.direction == PortType.OUTPUT, dst elif self.parentNode.parent is dst.parentNode: # target is parent output port assert dst.direction == PortType.INPUT, dst else: # target is child input port assert self.parentNode is dst.parentNode.parent, dst assert dst.direction == PortType.INPUT, dst return self.endpoints.append(dst)
def _collectPortTypeVariants( self ) -> List[Tuple[HdlPortItem, Dict[Tuple[Param, HValue], List[HdlType]]]]: res = [] param_variants = [paramsToValTuple(u) for u in self._units] for parent_port, port_variants in zip( self._ctx.ent.ports, zip(*(u._ctx.ent.ports for u in self._units))): param_val_to_t = {} for port_variant, params in zip(port_variants, param_variants): assert port_variant.name == parent_port.name, ( port_variant.name, parent_port.name) t = port_variant._dtype assert len(params) == len(self._params), (params, self._params) params = params._asdict() for p in self._params: p_val = params[p.hdl_name] types = param_val_to_t.setdefault((p, p_val), UniqList()) types.append(t) res.append((parent_port, param_val_to_t)) return res
def reduceUselessAssignments(root: LNode): """ Remove assignments if it is only a direct connection and can be replaced with direct link """ for n in root.children: if n.children: reduceUselessAssignments(n) to_remove = set() for n in root.children: if isinstance(n.originObj, HdlAssignmentContainer)\ and not n.originObj.indexes\ and len(n.west) == 1: src = n.originObj.src if isinstance(src, RtlSignalBase) and src.hidden: continue to_remove.add(n) srcPorts = [] dstPorts = [] edgesToRemove = UniqList() inP = getSinglePort(n.west) outP = getSinglePort(n.east) for e in inP.incomingEdges: sPort = e.src srcPorts.append((sPort, e.originObj)) edgesToRemove.append(e) for e in outP.outgoingEdges: dPort = e.dst dstPorts.append(dPort) edgesToRemove.append(e) for e in edgesToRemove: e.remove() for srcPort, originObj in srcPorts: for dstPort in dstPorts: root.addEdge(srcPort, dstPort, originObj=originObj) if to_remove: root.children = [n for n in root.children if n not in to_remove]
def portTryReduce(root: LNode, port: LPort): """ Check if majority of children is connected to same port if it is the case reduce children and connect this port instead children :note: use reduceUselessAssignments, extractSplits, flattenTrees before this function to maximize it's effect """ if not port.children: return for p in port.children: portTryReduce(root, p) target_nodes = {} ch_cnt = countDirectlyConnected(port, target_nodes) if not target_nodes: # disconnected port return new_target, children_edge_to_destroy = max(target_nodes.items(), key=lambda x: len(x[1])) if port.direction == new_target.direction: return # , (port, new_target, children_edge_to_destroy) cnt = len(children_edge_to_destroy) if cnt < ch_cnt / 2 or cnt == 1 and ch_cnt == 2: # too small or few shared connection to reduce return children_to_destroy = UniqList() on_target_children_to_destroy = UniqList() for child, edge in children_edge_to_destroy: if child.direction == PortType.OUTPUT: target_ch = edge.dsts elif child.direction == PortType.INPUT: target_ch = edge.srcs else: raise ValueError(child.direction) if len(target_ch) != 1: raise NotImplementedError("multiple connected nodes", target_ch) target_ch = target_ch[0] assert target_ch.parent is new_target, (target_ch, target_ch.parent, new_target, 'Wrong target:\n', edge.src, "\n", edge.dst, "\n", target_ch.parent, "\n", new_target) if child.direction == PortType.OUTPUT: edge.removeTarget(target_ch) elif child.direction == PortType.INPUT: edge.removeTarget(child) if not edge.srcs or not edge.dsts: edge.remove() if not target_ch.incomingEdges and not target_ch.outgoingEdges: # disconnect selected children from this port and target on_target_children_to_destroy.append(target_ch) if not child.incomingEdges and not child.outgoingEdges: children_to_destroy.append(child) for p in chain(children_to_destroy, on_target_children_to_destroy): p.connectedAsParent = True # destroy children of new target and this port if possible # port.children = [ # ch for ch in port.children if ch not in children_to_destroy] # new_target.children = [ # ch for ch in new_target.children if ch not in on_target_children_to_destroy] # if the port does have some sub ports which are an exceptions # from main port connection we have to add them # merge_non_reduced_ports(port, children_to_destroy) # merge_non_reduced_ports(new_target, on_target_children_to_destroy) # connect this port to new target as it was connected by children before # [TODO] names for new edges if port.direction == PortType.OUTPUT: root.addEdge(port, new_target) elif port.direction == PortType.INPUT: root.addEdge(new_target, port) else: raise NotImplementedError(port.direction)
class HdlSimulator(): """ Circuit simulator with support for external agents .. note:: *Signals without driver, constant driver, initial value* Every signal is initialized at start with its default value .. note:: *Communication between processes* For each output for every process there is an IoContainer instance which is container container of updates to signals. Every process has (generated) sensitivity-list. Process is reevaluated when there is a new value on any signal from sensitivity list. .. note: *Delta steps* Delta step is minimum quantum of changes in simulation, on the begining of delta step all reads are performed and on the end all writes are performed. Writes are causing revalution of HWprocesses which are schedueled into next delta step. Delta steps does not update time. When there is no process to reevaluate that means thereis nothing to do in delta step this delta step is considered as last in this time and time is shifted on begining of next event by simulator. .. note:: *Simulation inputs* HWprocess can not contain any blocking statement Simulation processes are written in python and can contain anything including blocking statements realized by yield sim.wait(time). (Using hdl as main simulator driver is not efficient.) .. note:: HWprocesses have lower priority than simulation processes this allows simplify logic of agents. When simulation process is executed, HW part did not anything in this time, Simulation process can prepare anything for HW part (= can write) if simulation process need to read, it has to yield simulator.updateComplete event first, process then will be wakened after reaction of HW in this time: agents are greatly simplified, they just need to yield simulator.waitOnCombUpdate() before first read and then can not write in this time. (Agent can be realized by multiple simulation processes.) :ivar now: actual simulation time :ivar _combUpdateDonePlaned: flag, True if event for combinational update is planed, this event is triggered when there are not any combinational signal updates in this time :ivar _applyValPlaned: flag, True if there is planed event for write stashed signal updates to signals :ivar _runSeqProcessesPlaned: flag, True if there is planed event for running sequential (rising/falling event) dependent processes to reevaluate :ivar _valuesToApply: is container of values which should be applied in this delta step :ivar _combProcsToRun: list of hdl processes to run :ivar _seqProcsToRun: list of rising/falling event dependent processes which should be evaluated after all combinational changes are applied :ivar _outputContainers: dictionary {SimSignal:IoContainer} for each hdl process :ivar _events: heap of simulation events and processes """ wait = Wait def __init__(self, config=None): super(HdlSimulator, self).__init__() if config is None: # default config config = HdlSimConfig() self.config = config self.now = 0.0 self._combUpdateDonePlaned = False self._applyValPlaned = False self._runSeqProcessesPlaned = False # (signal, value) tuples which should be applied before # new round of processes # will be executed self._valuesToApply = [] self._seqProcsToRun = UniqList() self._combProcsToRun = UniqList() # container of outputs for every process self._outputContainers = {} self._events = SimCalendar() @internal def _add_process(self, proc, priority) -> None: """ Schedule process on actual time with specified priority """ self._events.push(self.now, priority, proc) def waitOnCombUpdate(self) -> Event: """ Sim processes can wait on combUpdateDone by: yield sim.waitOnCombUpdate() Sim process is then woken up when all combinational updates are done in this delta step """ if not self._combUpdateDonePlaned: return self._scheduleCombUpdateDoneEv() else: return self.combUpdateDoneEv @internal def _addHdlProcToRun(self, trigger: SimSignal, proc) -> None: """ Add hdl process to execution queue :param trigger: instance of SimSignal :param proc: python generator function representing HDL process """ # first process in time has to plan executing of apply values on the # end of this time if not self._applyValPlaned: # (apply on end of this time to minimalize process reevaluation) self._scheduleApplyValues() if isEvDependentOn(trigger, proc): if self.now == 0: return # pass event dependent on startup self._seqProcsToRun.append(proc) else: self._combProcsToRun.append(proc) @internal def _initUnitSignals(self, unit: Unit) -> None: """ * Inject default values to simulation * Instantiate IOs for every process """ # set initial value to all signals and propagate it for s in unit._ctx.signals: if s.defVal.vldMask: v = s.defVal.clone() s.simUpdateVal(self, mkUpdater(v, False)) for u in unit._units: self._initUnitSignals(u) for p in unit._processes: self._addHdlProcToRun(None, p) for p, outputs in unit._outputs.items(): # name has to be explicit because it may be possible that signal # with has this name was replaced by signal from parent/child containerNames = list(map(lambda x: x[0], outputs)) class SpecificIoContainer(IoContainer): __slots__ = containerNames self._outputContainers[p] = SpecificIoContainer(outputs) @internal def __deleteCombUpdateDoneEv(self) -> Generator[None, None, None]: """ Callback called on combUpdateDoneEv finished """ self._combUpdateDonePlaned = False return yield @internal def _scheduleCombUpdateDoneEv(self) -> Event: """ Schedule combUpdateDoneEv event to let agents know that current delta step is ending and values from combinational logic are stable """ assert not self._combUpdateDonePlaned, self.now cud = Event(self) cud.process_to_wake.append(self.__deleteCombUpdateDoneEv()) self._add_process(cud, PRIORITY_AGENTS_UPDATE_DONE) self._combUpdateDonePlaned = True self.combUpdateDoneEv = cud return cud @internal def _scheduleApplyValues(self) -> None: """ Apply stashed values to signals """ assert not self._applyValPlaned, self.now self._add_process(self._applyValues(), PRIORITY_APPLY_COMB) self._applyValPlaned = True if self._runSeqProcessesPlaned: # if runSeqProcesses is already scheduled return assert not self._seqProcsToRun and not self._runSeqProcessesPlaned, self.now self._add_process(self._runSeqProcesses(), PRIORITY_APPLY_SEQ) self._runSeqProcessesPlaned = True @internal def _conflictResolveStrategy(self, newValue: set)\ -> Tuple[Callable[[Value], bool], bool]: """ This functions resolves write conflicts for signal :param actionSet: set of actions made by process """ invalidate = False resLen = len(newValue) if resLen == 3: # update for item in array val, indexes, isEvDependent = newValue return (mkArrayUpdater(val, indexes, invalidate), isEvDependent) else: # update for simple signal val, isEvDependent = newValue return (mkUpdater(val, invalidate), isEvDependent) @internal def _runCombProcesses(self) -> None: """ Delta step for combinational processes """ for proc in self._combProcsToRun: cont = self._outputContainers[proc] proc(self, cont) for sigName, sig in cont._all_signals: newVal = getattr(cont, sigName) if newVal is not None: res = self._conflictResolveStrategy(newVal) # prepare update updater, isEvDependent = res self._valuesToApply.append( (sig, updater, isEvDependent, proc)) setattr(cont, sigName, None) # else value is latched self._combProcsToRun = UniqList() @internal def _runSeqProcesses(self) -> Generator[None, None, None]: """ Delta step for event dependent processes """ updates = [] for proc in self._seqProcsToRun: try: outContainer = self._outputContainers[proc] except KeyError: # processes does not have to have outputs outContainer = None proc(self, outContainer) if outContainer is not None: updates.append(outContainer) self._seqProcsToRun = UniqList() self._runSeqProcessesPlaned = False for cont in updates: for sigName, sig in cont._all_signals: newVal = getattr(cont, sigName) if newVal is not None: v = self._conflictResolveStrategy(newVal) updater, _ = v sig.simUpdateVal(self, updater) setattr(cont, sigName, None) return yield @internal def _applyValues(self) -> Generator[None, None, None]: """ Perform delta step by writing stacked values to signals """ va = self._valuesToApply self._applyValPlaned = False # log if there are items to log lav = self.config.logApplyingValues if va and lav: lav(self, va) self._valuesToApply = [] # apply values to signals, values can overwrite each other # but each signal should be driven by only one process and # it should resolve value collision addSp = self._seqProcsToRun.append for s, vUpdater, isEventDependent, comesFrom in va: if isEventDependent: # now=0 and this was process initialization or async reg addSp(comesFrom) else: # regular combinational process s.simUpdateVal(self, vUpdater) self._runCombProcesses() # processes triggered from simUpdateVal can add new values if self._valuesToApply and not self._applyValPlaned: self._scheduleApplyValues() return yield def read(self, sig) -> Value: """ Read value from signal or interface """ try: v = sig._val except AttributeError: v = sig._sigInside._val return v.clone() def write(self, val, sig: SimSignal) -> None: """ Write value to signal or interface. """ # get target RtlSignal try: simSensProcs = sig.simSensProcs except AttributeError: sig = sig._sigInside simSensProcs = sig.simSensProcs # type cast of input value t = sig._dtype if isinstance(val, Value): v = val.clone() v = v._auto_cast(t) else: v = t.fromPy(val) # can not update value in signal directly due singnal proxies sig.simUpdateVal(self, lambda curentV: (valueHasChanged(curentV, v), v)) if not self._applyValPlaned: if not (simSensProcs or sig.simRisingSensProcs or sig.simFallingSensProcs): # signal value was changed but there are no sensitive processes # to it because of this _applyValues is never planed # and should be self._scheduleApplyValues() elif (sig._writeCallbacks or sig._writeCallbacksToEn): # signal write did not caused any change on any other signal # but there are still simulation agets waiting on # updateComplete event self._scheduleApplyValues() def run(self, until: float) -> None: """ Run simulation until specified time :note: can be used to run simulation again after it ends from time when it ends """ assert until > self.now events = self._events schedule = events.push next_event = events.pop # add handle to stop simulation schedule(until, PRIORITY_URGENT, raise_StopSimulation(self)) try: # for all events while True: nextTime, priority, process = next_event() self.now = nextTime # process is python generator or Event if isinstance(process, Event): process = iter(process) # run process or activate processes dependent on Event while True: try: ev = next(process) except StopIteration: break # if process requires waiting put it back in queue if isinstance(ev, Wait): # nextTime is self.now schedule(nextTime + ev.time, priority, process) break elif isinstance(ev, Event): # process going to wait for event # if ev.process_to_wake is None event was already # destroyed ev.process_to_wake.append(process) break else: # else this process spoted new process # and it has to be put in queue schedule(nextTime, priority, ev) except StopSimumulation: return def add_process(self, proc) -> None: """ Add process to events with default priority on current time """ self._events.push(self.now, PRIORITY_NORMAL, proc) def simUnit(self, synthesisedUnit: Unit, until: float, extraProcesses=[]): """ Run simulation for Unit instance """ beforeSim = self.config.beforeSim if beforeSim is not None: beforeSim(self, synthesisedUnit) add_proc = self.add_process for p in extraProcesses: add_proc(p(self)) self._initUnitSignals(synthesisedUnit) self.run(until)
class HdlStatement(HdlObject): """ :ivar _is_completly_event_dependent: statement does not have any combinational statement :ivar _now_is_event_dependent: statement is event (clk) dependent :ivar parentStm: parent instance of HdlStatement or None :ivar _inputs: UniqList of input signals for this statement :ivar _outputs: UniqList of output signals for this statement :ivar _sensitivity: UniqList of input signals or (rising/falling) operator :ivar _enclosed_for: set of outputs for which this statement is enclosed (for which there is not any unused branch) :ivar rank: number of used branches in statement, used as prefilter for statement comparing """ def __init__(self, parentStm=None, sensitivity=None, is_completly_event_dependent=False): self._is_completly_event_dependent = is_completly_event_dependent self._now_is_event_dependent = is_completly_event_dependent self.parentStm = parentStm self._inputs = UniqList() self._outputs = UniqList() self._enclosed_for = None self._sensitivity = sensitivity self.rank = 0 @internal def _clean_signal_meta(self): """ Clean informations about enclosure for outputs and sensitivity of this statement """ self._enclosed_for = None self._sensitivity = None for stm in self._iter_stms(): stm._clean_signal_meta() @internal def _collect_io(self) -> None: """ Collect inputs/outputs from all child statements to :py:attr:`~_input` / :py:attr:`_output` attribute on this object """ in_add = self._inputs.extend out_add = self._outputs.extend for stm in self._iter_stms(): in_add(stm._inputs) out_add(stm._outputs) @internal @staticmethod def _cut_off_drivers_of_list(sig: RtlSignalBase, statements: List["HdlStatement"], keep_mask: List[bool], new_statements: List["HdlStatement"]): """ Cut all logic from statements which drives signal sig. :param sig: signal which drivers should be removed :param statements: list of statements to filter :param keep_mask: list of flags if True statements was driver only of sig :param new_statements: output list of filtered statements :return: True if all input statements were reduced """ all_cut_off = True for stm in statements: newStm = stm._cut_off_drivers_of(sig) keep = True if newStm is None: # statement is des not have drivers of sig all_cut_off = False elif newStm is stm: # statement drives only sig keep = False new_statements.append(newStm) else: # statement was splited on multiple statements all_cut_off = False new_statements.append(newStm) keep_mask.append(keep) return all_cut_off @internal def _discover_enclosure(self) -> None: """ Discover all outputs for which is this statement enclosed _enclosed_for property (has driver in all code branches) """ raise NotImplementedError( "This method should be implemented" " on class of statement", self.__class__, self) @internal @staticmethod def _discover_enclosure_for_statements(statements: List['HdlStatement'], outputs: List['RtlSignalBase']): """ Discover enclosure for list of statements :param statements: list of statements in one code branch :param outputs: list of outputs which should be driven from this statement list :return: set of signals for which this statement list have always some driver (is enclosed) """ result = set() if not statements: return result for stm in statements: stm._discover_enclosure() for o in outputs: has_driver = False for stm in statements: if o in stm._outputs: assert not has_driver has_driver = False if o in stm._enclosed_for: result.add(o) else: pass return result @internal def _discover_sensitivity(self, seen: set) -> None: """ discover all sensitivity signals and store them to _sensitivity property """ raise NotImplementedError( "This method should be implemented" " on class of statement", self.__class__, self) @internal def _discover_sensitivity_sig(self, signal: RtlSignalBase, seen: set, ctx: SensitivityCtx): casualSensitivity = set() signal._walk_sensitivity(casualSensitivity, seen, ctx) if not ctx.contains_ev_dependency: # if event dependent sensitivity found do not add other sensitivity ctx.extend(casualSensitivity) @internal def _discover_sensitivity_seq(self, signals: List[RtlSignalBase], seen: set, ctx: SensitivityCtx)\ -> None: """ Discover sensitivity for list of signals """ casualSensitivity = set() for s in signals: s._walk_sensitivity(casualSensitivity, seen, ctx) if ctx.contains_ev_dependency: break # if event dependent sensitivity found do not add other sensitivity if not ctx.contains_ev_dependency: ctx.extend(casualSensitivity) @internal def _get_rtl_context(self): """ get RtlNetlist context from signals """ for sig in chain(self._inputs, self._outputs): if sig.ctx: return sig.ctx else: # Param instances does not have context continue raise HwtSyntaxError( "Statement does not have any signal in any context", self) def _iter_stms(self): """ :return: iterator over all children statements """ raise NotImplementedError( "This method should be implemented" " on class of statement", self.__class__, self) @internal def _on_reduce(self, self_reduced: bool, io_changed: bool, result_statements: List["HdlStatement"]) -> None: """ Update signal IO after reduce attempt :param self_reduced: if True this object was reduced :param io_changed: if True IO of this object may changed and has to be updated :param result_statements: list of statements which are result of reduce operation on this statement """ parentStm = self.parentStm if self_reduced: was_top = parentStm is None # update signal drivers/endpoints if was_top: # disconnect self from signals ctx = self._get_rtl_context() ctx.statements.remove(self) ctx.statements.update(result_statements) for i in self._inputs: i.endpoints.discard(self) for o in self._outputs: o.drivers.remove(self) for stm in result_statements: stm.parentStm = parentStm if parentStm is None: # connect signals to child statements for inp in stm._inputs: inp.endpoints.append(stm) for outp in stm._outputs: outp.drivers.append(stm) else: # parent has to update it's inputs/outputs if io_changed: self._inputs = UniqList() self._outputs = UniqList() self._collect_io() @internal def _on_merge(self, other): """ After merging statements update IO, sensitivity and context :attention: rank is not updated """ self._inputs.extend(other._inputs) self._outputs.extend(other._outputs) if self._sensitivity is not None: self._sensitivity.extend(other._sensitivity) else: assert other._sensitivity is None if self._enclosed_for is not None: self._enclosed_for.update(other._enclosed_for) else: assert other._enclosed_for is None other_was_top = other.parentStm is None if other_was_top: other._get_rtl_context().statements.remove(other) for s in other._inputs: s.endpoints.discard(other) s.endpoints.append(self) for s in other._outputs: s.drivers.discard(other) s.drivers.append(self) @internal def _try_reduce(self) -> Tuple[List["HdlStatement"], bool]: raise NotImplementedError( "This method should be implemented" " on class of statement", self.__class__, self) def _is_enclosed(self) -> bool: """ :return: True if every branch in statement is covered for all signals else False """ return len(self._outputs) == len(self._enclosed_for) @internal def _is_mergable(self, other: "HdlStatement") -> bool: if self is other: raise ValueError("Can not merge statement with itself") else: raise NotImplementedError( "This method should be implemented" " on class of statement", self.__class__, self) @internal @classmethod def _is_mergable_statement_list(cls, stmsA, stmsB): """ Walk statements and compare if they can be merged into one statement list """ if stmsA is None and stmsB is None: return True elif stmsA is None or stmsB is None: return False a_it = iter(stmsA) b_it = iter(stmsB) a = _get_stm_with_branches(a_it) b = _get_stm_with_branches(b_it) while a is not None or b is not None: if a is None or b is None or not a._is_mergable(b): return False a = _get_stm_with_branches(a_it) b = _get_stm_with_branches(b_it) # lists are empty return True @internal @staticmethod def _merge_statements(statements: List["HdlStatement"])\ -> Tuple[List["HdlStatement"], int]: """ Merge statements in list to remove duplicated if-then-else trees :return: tuple (list of merged statements, rank decrease due merging) :note: rank decrease is sum of ranks of reduced statements :attention: statement list has to me mergable """ order = {} for i, stm in enumerate(statements): order[stm] = i new_statements = [] rank_decrease = 0 for rank, stms in groupedby(statements, lambda s: s.rank): if rank == 0: new_statements.extend(stms) else: if len(stms) == 1: new_statements.extend(stms) continue # try to merge statements if they are same condition tree for iA, stmA in enumerate(stms): if stmA is None: continue for iB, stmB in enumerate(islice(stms, iA + 1, None)): if stmB is None: continue if stmA._is_mergable(stmB): rank_decrease += stmB.rank stmA._merge_with_other_stm(stmB) stms[iA + 1 + iB] = None new_statements.append(stmA) new_statements.sort(key=lambda stm: order[stm]) return new_statements, rank_decrease @internal @staticmethod def _merge_statement_lists(stmsA: List["HdlStatement"], stmsB: List["HdlStatement"])\ -> List["HdlStatement"]: """ Merge two lists of statements into one :return: list of merged statements """ if stmsA is None and stmsB is None: return None tmp = [] a_it = iter(stmsA) b_it = iter(stmsB) a = None b = None a_empty = False b_empty = False while not a_empty and not b_empty: while not a_empty: a = next(a_it, None) if a is None: a_empty = True break elif a.rank == 0: # simple statement does not require merging tmp.append(a) a = None else: break while not b_empty: b = next(b_it, None) if b is None: b_empty = True break elif b.rank == 0: # simple statement does not require merging tmp.append(b) b = None else: break if a is not None or b is not None: if b is None: a = b b = None if a is not None and b is not None: a._merge_with_other_stm(b) tmp.append(a) a = None b = None return tmp @internal @staticmethod def _try_reduce_list(statements: List["HdlStatement"]): """ Simplify statements in the list """ io_change = False new_statements = [] for stm in statements: reduced, _io_change = stm._try_reduce() new_statements.extend(reduced) io_change |= _io_change new_statements, rank_decrease = HdlStatement._merge_statements( new_statements) return new_statements, rank_decrease, io_change @internal def _on_parent_event_dependent(self): """ After parent statement become event dependent propagate event dependency flag to child statements """ if not self._is_completly_event_dependent: self._is_completly_event_dependent = True for stm in self._iter_stms(): stm._on_parent_event_dependent() @internal def _set_parent_stm(self, parentStm: "HdlStatement"): """ Assign parent statement and propagate dependency flags if necessary """ was_top = self.parentStm is None self.parentStm = parentStm if not self._now_is_event_dependent\ and parentStm._now_is_event_dependent: self._on_parent_event_dependent() topStatement = parentStm while topStatement.parentStm is not None: topStatement = topStatement.parentStm parent_out_add = topStatement._outputs.append parent_in_add = topStatement._inputs.append if was_top: for inp in self._inputs: inp.endpoints.discard(self) inp.endpoints.append(topStatement) parent_in_add(inp) for outp in self._outputs: outp.drivers.discard(self) outp.drivers.append(topStatement) parent_out_add(outp) ctx = self._get_rtl_context() ctx.statements.discard(self) parentStm.rank += self.rank @internal def _register_stements(self, statements: List["HdlStatement"], target: List["HdlStatement"]): """ Append statements to this container under conditions specified by condSet """ for stm in flatten(statements): assert stm.parentStm is None, stm stm._set_parent_stm(self) target.append(stm) def isSame(self, other: "HdlStatement") -> bool: """ :return: True if other has same meaning as self """ raise NotImplementedError( "This method should be implemented" " on class of statement", self.__class__, self) @internal def _destroy(self): """ Disconnect this statement from signals and delete it from RtlNetlist context :attention: signal endpoints/drivers will be altered that means they can not be used for iteration """ ctx = self._get_rtl_context() for i in self._inputs: i.endpoints.discard(self) for o in self._outputs: o.drivers.remove(self) ctx.statements.remove(self) @internal def _replace_input(self, toReplace: RtlSignalBase, replacement: RtlSignalBase) -> None: """ Replace input signal with another :note: sensitivity/endoints are actualized """ raise NotImplementedError() @internal def _replace_input_update_sensitivity_and_enclosure( self, toReplace: RtlSignalBase, replacement: RtlSignalBase): if self._sensitivity is not None: if self._sensitivity.discard(toReplace): self._sensitivity.append(replacement) if self._enclosed_for is not None: if self._enclosed_for.discard(toReplace): self._enclosed_for.append(replacement)
def extend(self, items): UniqList.extend(self, items) if isinstance(items, SensitivityCtx): self.contains_ev_dependency |= items.contains_ev_dependency
def clear(self): UniqList.clear(self) self.contains_ev_dependency = False
def _statements_to_HWProcesses(_statements, tryToSolveCombLoops)\ -> Generator[HWProcess, None, None]: assert _statements # try to simplify statements proc_statements = [] for _stm in _statements: stms, _ = _stm._try_reduce() proc_statements.extend(stms) outputs = UniqList() _inputs = UniqList() sensitivity = UniqList() enclosed_for = set() for _stm in proc_statements: seen = set() _stm._discover_sensitivity(seen) _stm._discover_enclosure() outputs.extend(_stm._outputs) _inputs.extend(_stm._inputs) sensitivity.extend(_stm._sensitivity) enclosed_for.update(_stm._enclosed_for) enclosure_values = {} for sig in outputs: # inject nopVal if needed if sig._useNopVal: n = sig._nopVal enclosure_values[sig] = n if enclosure_values: do_enclose_for = list(where(outputs, lambda o: o in enclosure_values)) fill_stm_list_with_enclosure(None, enclosed_for, proc_statements, do_enclose_for, enclosure_values) if proc_statements: for o in outputs: assert not o.hidden, o seen = set() inputs = UniqList() for i in _inputs: inputs.extend(i._walk_public_drivers(seen)) intersect = outputs.intersection_set(sensitivity) if intersect: if not tryToSolveCombLoops: raise HwtSyntaxError( "Combinational loop on signal(s)", intersect) # try to solve combinational loops by separating drivers of signals # from statements for sig in intersect: proc_statements, proc_stms_select = cut_off_drivers_of( sig, proc_statements) yield from _statements_to_HWProcesses(proc_stms_select, False) if proc_statements: yield from _statements_to_HWProcesses(proc_statements, False) else: name = name_for_process(outputs) yield HWProcess("assig_process_" + name, proc_statements, sensitivity, inputs, outputs) else: assert not outputs # this can happen e.g. when If does not contains any Assignment pass
def __init__(self, initSeq=None): UniqList.__init__(self, initSeq=initSeq) self.contains_ev_dependency = False
def toRtl(unitOrCls: Unit, name: str=None, serializer: GenericSerializer=VhdlSerializer, targetPlatform=DummyPlatform(), saveTo: str=None): """ Convert unit to RTL using specified serializer :param unitOrCls: unit instance or class, which should be converted :param name: name override of top unit (if is None name is derived form class name) :param serializer: serializer which should be used for to RTL conversion :param targetPlatform: metainformatins about target platform, distributed on every unit under _targetPlatform attribute before Unit._impl() is called :param saveTo: directory where files should be stored If None RTL is returned as string. :raturn: if saveTo returns RTL string else returns list of file names which were created """ if not isinstance(unitOrCls, Unit): u = unitOrCls() else: u = unitOrCls u._loadDeclarations() if name is not None: assert isinstance(name, str) u._name = name globScope = serializer.getBaseNameScope() mouduleScopes = {} # unitCls : unitobj serializedClasses = {} # (unitCls, paramsValues) : unitObj # where paramsValues are dict name:value serializedConfiguredUnits = {} doSerialize = True createFiles = saveTo is not None if createFiles: os.makedirs(saveTo, exist_ok=True) files = UniqList() else: codeBuff = [] for obj in u._toRtl(targetPlatform): doSerialize = serializer.serializationDecision( obj, serializedClasses, serializedConfiguredUnits) if doSerialize: if isinstance(obj, Entity): s = globScope.fork(1) s.setLevel(2) ctx = serializer.getBaseContext() ctx.scope = s mouduleScopes[obj] = ctx ctx.currentUnit = obj.origin sc = serializer.Entity(obj, ctx) if createFiles: fName = obj.name + serializer.fileExtension fileMode = 'w' elif isinstance(obj, Architecture): try: ctx = mouduleScopes[obj.entity] except KeyError: raise SerializerException( "Entity should be serialized" " before architecture of %s" % (obj.getEntityName())) sc = serializer.Architecture(obj, ctx) if createFiles: fName = obj.getEntityName() + serializer.fileExtension fileMode = 'a' else: if hasattr(obj, "_hdlSources"): for fn in obj._hdlSources: if isinstance(fn, str): shutil.copy2(fn, saveTo) files.append(fn) continue else: sc = serializer.asHdl(obj) if sc: if createFiles: fp = os.path.join(saveTo, fName) files.append(fp) with open(fp, fileMode) as f: if fileMode == 'a': f.write("\n") f.write( serializer.formatter(sc) ) else: codeBuff.append(sc) elif not createFiles: try: name = '"%s"' % obj.name except AttributeError: name = "" codeBuff.append(serializer.comment( "Object of class %s, %s was not serialized as specified" % ( obj.__class__.__name__, name))) if createFiles: return files else: return serializer.formatter( "\n".join(codeBuff) )
def _statements_to_HdlStatementBlocks(_statements, tryToSolveCombLoops)\ -> Generator[HdlStatementBlock, None, None]: assert _statements # try to simplify statements proc_statements = [] for _stm in _statements: _stm._clean_signal_meta() stms, _ = _stm._try_reduce() proc_statements.extend(stms) if not proc_statements: return outputs = UniqList() _inputs = UniqList() sensitivity = UniqList() enclosed_for = set() _proc_statements = [] for _stm in proc_statements: seen = set() _stm._discover_sensitivity(seen) _stm._discover_enclosure() if _stm._outputs: # remove a statement entirely if it has no ouput # (empty if statment or something similar) # simulation only processes should not be processed by this function # and process should always drive something, unless it is useless outputs.extend(_stm._outputs) _inputs.extend(_stm._inputs) sensitivity.extend(_stm._sensitivity) enclosed_for.update(_stm._enclosed_for) _proc_statements.append(_stm) proc_statements = _proc_statements if not proc_statements: # this can happen e.g. when If does not contains any Assignment return sensitivity_recompute = False enclosure_recompute = False enclosure_values = {} for sig in outputs: # inject nop_val if needed if sig._nop_val is not NO_NOPVAL and sig not in enclosed_for: enclosure_recompute = True n = sig._nop_val enclosure_values[sig] = n if not isinstance(n, Value): _inputs.append(n) sensitivity_recompute = True if enclosure_recompute: # we have some enclosure values, try fill missing code branches with # this values do_enclose_for = [o for o in outputs if o in enclosure_values] fill_stm_list_with_enclosure(None, enclosed_for, proc_statements, do_enclose_for, enclosure_values) if enclosure_recompute or sensitivity_recompute: for _stm in proc_statements: _stm._clean_signal_meta() seen = set() _stm._discover_sensitivity(seen) _stm._discover_enclosure() if sensitivity_recompute: sensitivity = UniqList() for _stm in proc_statements: sensitivity.extend(_stm._sensitivity) for o in outputs: assert not o.hidden, o seen = set() inputs = UniqList() for i in _inputs: inputs.extend(i._walk_public_drivers(seen)) intersect = outputs.intersection_set(sensitivity) if intersect: # there is a combinational loop inside a single process which # can not be solved by separation of statments in process if not tryToSolveCombLoops: raise HwtSyntaxError( "Combinational loop on signal(s)", intersect) # try to solve combinational loops by separating drivers of signals # from statements for sig in intersect: proc_statements, proc_stms_select = cut_off_drivers_of( sig, proc_statements) yield from _statements_to_HdlStatementBlocks(proc_stms_select, False) if proc_statements: yield from _statements_to_HdlStatementBlocks(proc_statements, False) else: # no combinational loops, wrap current statemetns to a process instance name = name_for_process(outputs) yield HdlStatementBlock("assig_process_" + name, proc_statements, sensitivity, inputs, outputs)
def extract_part_drivers_stm(stm: HdlStatement, signal_parts: Dict[RtlSignal, List[Tuple[RtlSignal, List[HValue]]]]): if isinstance(stm, Assignment): if stm.indexes and len(stm.indexes) == 1: dst = stm.dst parts = signal_parts.get(dst, None) if parts is None: return False indexes = _format_indexes(stm.indexes) new_dsts, do_remove_stm = parts[indexes] if isinstance(new_dsts, list): assert len(new_dsts) > 1, (dst, new_dsts, stm) assert not do_remove_stm, (dst, new_dsts, stm) # the driven slice was split to multiple sub slices replacement = [] dst_offset = new_dsts[0][-1][1] for i in new_dsts: new_dst = parts[i][0] new_src = stm.src for _i in i: high, low = _i[0] - dst_offset, _i[1] - dst_offset assert high > 0 and low >= 0, dst_offset assert high > low, (dst, stm, (high, low)) new_src = new_src[high:low] a = new_dst(new_src) replacement.append(a) # it has to hav parent statement because it needs to be nested # because otherwise it would not have some overlapping parts driven diferently # inder some condition stm.parentStm._replace_child_statement(stm, replacement, False) elif do_remove_stm: # remove current assignment because we are using src directly assert stm.parentStm is None, stm stm._destroy() else: # rewrite the Assignment instance to use new dst replacement = [ new_dsts(stm.src), ] stm.parentStm._replace_child_statement(stm, replacement, False) return True elif isinstance(stm, (IfContainer, SwitchContainer, HdlStatementBlock)): modified = False for _stm in stm._iter_stms(): modified |= extract_part_drivers_stm(_stm, signal_parts) if modified: assert not stm._enclosed_for, "_enclosed_for is expected not to be initialized yet" outputs = stm._outputs inputs = stm._inputs stm._outputs = UniqList() stm._inputs = UniqList() stm._collect_io() if stm.parentStm is None: for o in outputs: if o not in stm._outputs: o.drivers.remove(stm) for i in inputs: if i not in stm._inputs: i.endpoints.remove(stm) return True else: raise NotImplementedError("Unknown statement ", stm) return False
class Packager(object): """ Ipcore packager """ def __init__(self, topUnit: Unit, name: str = None, extraVhdlFiles: List[str] = [], extraVerilogFiles: List[str] = [], serializer=VhdlSerializer, targetPlatform=DummyPlatform()): assert not topUnit._wasSynthetised() self.topUnit = topUnit self.serializer = serializer self.targetPlatform = targetPlatform if name: self.name = name else: self.name = self.topUnit._getDefaultName() self.hdlFiles = UniqList() for f in extraVhdlFiles: self.hdlFiles.append(f) for f in extraVerilogFiles: self.hdlFiles.append(f) def saveHdlFiles(self, srcDir): path = os.path.join(srcDir, self.name) try: os.makedirs(path) except OSError: # wipe if exists shutil.rmtree(path) os.makedirs(path) files = self.hdlFiles self.hdlFiles = toRtl(self.topUnit, saveTo=path, name=self.name, serializer=self.serializer, targetPlatform=self.targetPlatform) for srcF in files: dst = os.path.join( path, os.path.relpath(srcF, srcDir).replace('../', '')) os.makedirs(os.path.dirname(dst), exist_ok=True) shutil.copy(srcF, dst) self.hdlFiles.append(dst) def mkAutoGui(self): gui = GuiBuilder() p0 = gui.page("Main") handlers = [] for g in self.topUnit._entity.generics: p0.param(g.name) for fn in paramManipulatorFns(g.name): handlers.append(fn) with open(self.guiFile, "w") as f: f.write(gui.asTcl()) for h in handlers: f.write('\n\n') f.write(str(h)) def createPackage(self, repoDir, vendor="hwt", library="mylib", description=None): ''' synthetise hdl if needed copy hdl files create gui file create component.xml ''' ip_dir = os.path.join(repoDir, self.name + "/") if os.path.exists(ip_dir): shutil.rmtree(ip_dir) ip_srcPath = os.path.join(ip_dir, "src") tclPath = os.path.join(ip_dir, "xgui") guiFile = os.path.join(tclPath, "gui.tcl") for d in [ip_dir, ip_srcPath, tclPath]: os.makedirs(d) self.saveHdlFiles(ip_srcPath) self.guiFile = guiFile self.mkAutoGui() c = Component() c._files = [relpath(p, ip_dir) for p in sorted(self.hdlFiles)] + \ [relpath(guiFile, ip_dir)] c.vendor = vendor c.library = library if description is None: c.description = self.name + "_v" + c.version else: c.description = description c.asignTopUnit(self.topUnit) xml_str = prettify(c.xml()) with open(ip_dir + "component.xml", "w") as f: f.write(xml_str) quartus_tcl_str = c.quartus_tcl() with open(ip_dir + "component_hw.tcl", "w") as f: f.write(quartus_tcl_str)
class HdlStatement(HdlObject): """ :ivar ~._event_dependent_from_branch: index of code branch if statement is event (clk) dependent else None :ivar ~.parentStm: parent instance of HdlStatement or None :ivar ~._inputs: UniqList of input signals for this statement :ivar ~._outputs: UniqList of output signals for this statement :ivar ~._sensitivity: UniqList of input signals or (rising/falling) operator :ivar ~._enclosed_for: set of outputs for which this statement is enclosed (for which there is not any unused branch) :ivar ~.rank: number of used branches in statement, used as pre-filter for statement comparing """ def __init__(self, parentStm: Optional["HdlStatement"] = None, sensitivity: Optional[UniqList] = None, event_dependent_from_branch: Optional[int] = None): assert event_dependent_from_branch is None or isinstance( event_dependent_from_branch, int), event_dependent_from_branch self._event_dependent_from_branch = event_dependent_from_branch self.parentStm = parentStm self._inputs = UniqList() self._outputs = UniqList() self._enclosed_for = None self._sensitivity = sensitivity self.rank = 0 @internal def _clean_signal_meta(self): """ Clean informations about enclosure for outputs and sensitivity of this statement """ self._enclosed_for = None self._sensitivity = None for stm in self._iter_stms(): stm._clean_signal_meta() @internal def _collect_io(self) -> None: """ Collect inputs/outputs from all child statements to :py:attr:`~_input` / :py:attr:`_output` attribute on this object """ in_add = self._inputs.extend out_add = self._outputs.extend for stm in self._iter_stms(): in_add(stm._inputs) out_add(stm._outputs) @internal def _collect_inputs(self) -> None: """ Collect inputs from all child statements to :py:attr:`~_input` attribute on this object """ in_add = self._inputs.extend for stm in self._iter_stms(): in_add(stm._inputs) @internal def _collect_outputs(self) -> None: """ Collect inputs from all child statements to :py:attr:`_output` attribute on this object """ out_add = self._outputs.extend for stm in self._iter_stms(): out_add(stm._outputs) @internal def _cut_off_drivers_of( self, sig: RtlSignalBase ) -> Union[None, "HdlStatement", List["HdlStatement"]]: """ Cut all logic from statements which drives signal sig. :param sig: signal which drivers should be removed :return: A statement or statement list which was cut off from the original statement """ raise NotImplementedError( "This is an abstract method and it should be implemented in child class" ) @internal def _cut_off_drivers_of_regenerate_io(self, cut_off_sig: RtlSignalBase, cut_of_smt: "HdlStatement"): """ Update _inputs/_outputs after some part of statement was cut of :param cut_off_sig: a signal which driver is a cut_of_stm :param cut_of_smt: the statement wich was cut off from original statement (selected by cut_off_sig) """ # update io of this self._outputs.remove(cut_off_sig) if cut_of_smt._inputs: # update inputs on this self._inputs.clear() self._collect_inputs() if self.parentStm is None: for i in cut_of_smt._inputs: if i not in self._inputs: i.endpoints.remove(self) if self.parentStm is None: cut_off_sig.drivers.append(cut_of_smt) @internal def _discover_enclosure(self) -> None: """ Discover all outputs for which is this statement enclosed _enclosed_for property (has driver in all code branches) """ raise NotImplementedError( "This method should be implemented" " on class of statement", self.__class__, self) @internal def _discover_sensitivity(self, seen: set) -> None: """ discover all sensitivity signals and store them to _sensitivity property """ raise NotImplementedError( "This method should be implemented" " on class of statement", self.__class__, self) @internal def _get_rtl_context(self): """ get RtlNetlist context from signals """ for sig in chain(self._inputs, self._outputs): if sig.ctx: return sig.ctx else: # Param instances does not have context continue raise HwtSyntaxError( "Statement does not have any signal in any context", self) def _iter_stms(self): """ :return: iterator over all children statements """ raise NotImplementedError( "This method should be implemented" " on class of statement", self.__class__, self) @internal def _on_reduce(self, self_reduced: bool, io_changed: bool, result_statements: List["HdlStatement"]) -> None: """ Update signal IO after reduce attempt :param self_reduced: if True this object was reduced :param io_changed: if True IO of this object may changed and has to be updated :param result_statements: list of statements which are result of reduce operation on this statement """ parentStm = self.parentStm if self_reduced: was_top = parentStm is None # update signal drivers/endpoints if was_top: # disconnect self from signals ctx = self._get_rtl_context() ctx.statements.remove(self) ctx.statements.update(result_statements) for i in self._inputs: i.endpoints.discard(self) for o in self._outputs: o.drivers.remove(self) for stm in result_statements: stm.parentStm = parentStm if parentStm is None: # connect signals to child statements for inp in stm._inputs: inp.endpoints.append(stm) for outp in stm._outputs: outp.drivers.append(stm) else: # parent has to update it's inputs/outputs if io_changed: self._inputs = UniqList() self._outputs = UniqList() self._collect_io() @internal def _on_merge(self, other): """ After merging statements update IO, sensitivity and context :attention: rank is not updated """ self._inputs.extend(other._inputs) self._outputs.extend(other._outputs) if self._sensitivity is not None: self._sensitivity.extend(other._sensitivity) else: assert other._sensitivity is None if self._enclosed_for is not None: self._enclosed_for.update(other._enclosed_for) else: assert other._enclosed_for is None other_was_top = other.parentStm is None if other_was_top: other._get_rtl_context().statements.remove(other) for s in other._inputs: s.endpoints.discard(other) s.endpoints.append(self) for s in other._outputs: s.drivers.discard(other) s.drivers.append(self) @internal def _try_reduce(self) -> Tuple[List["HdlStatement"], bool]: raise NotImplementedError( "This method should be implemented" " on class of statement", self.__class__, self) def _is_enclosed(self) -> bool: """ :return: True if every branch in statement assignas to all output signals else False """ return len(self._outputs) == len(self._enclosed_for) @internal def _is_mergable(self, other: "HdlStatement") -> bool: if self is other: raise ValueError("Can not merge statement with itself") else: raise NotImplementedError( "This method should be implemented" " on class of statement", self.__class__, self) @internal def _on_parent_event_dependent(self): """ After parent statement become event dependent propagate event dependency flag to child statements """ if self._event_dependent_from_branch != 0: self._event_dependent_from_branch = 0 for stm in self._iter_stms(): stm._on_parent_event_dependent() @internal def _set_parent_stm(self, parentStm: "HdlStatement"): """ Assign parent statement and propagate dependency flags if necessary """ was_top = self.parentStm is None self.parentStm = parentStm if self._event_dependent_from_branch is None\ and parentStm._event_dependent_from_branch is not None: self._on_parent_event_dependent() topStatement = parentStm parents = [] while True: parents.append(topStatement) if topStatement.parentStm is None: break topStatement = topStatement.parentStm if was_top: for inp in self._inputs: inp.endpoints.discard(self) inp.endpoints.append(topStatement) for p in parents: p._inputs.append(inp) for outp in self._outputs: outp.drivers.discard(self) outp.drivers.append(topStatement) for p in parents: p._outputs.append(outp) ctx = self._get_rtl_context() ctx.statements.discard(self) parentStm.rank += self.rank @internal def _register_stements(self, statements: List["HdlStatement"], target: List["HdlStatement"]): """ Append statements to this container """ for stm in flatten(statements): assert stm.parentStm is None, ( "HdlStatement instance has to have only a single parent", stm) stm._set_parent_stm(self) target.append(stm) def isSame(self, other: "HdlStatement") -> bool: """ :return: True if other has same meaning as self """ raise NotImplementedError( "This method should be implemented in child class", self.__class__, self) @internal def _destroy(self): """ Disconnect this statement from signals and delete it from RtlNetlist context :attention: signal endpoints/drivers will be altered that means they can not be used for iteration """ for i in self._inputs: i.endpoints.discard(self) if self.parentStm is None: ctx = self._get_rtl_context() for o in self._outputs: o.drivers.remove(self) ctx.statements.remove(self) self.parentStm = None @internal def _replace_input(self, toReplace: RtlSignalBase, replacement: RtlSignalBase) -> None: """ Replace input signal with another :note: sensitivity/endoints are actualized """ raise NotImplementedError( "This method should be implemented in child class", self.__class__, self) @internal def _replace_input_update_sensitivity_and_enclosure( self, toReplace: RtlSignalBase, replacement: RtlSignalBase): if self._sensitivity is not None: if self._sensitivity.discard(toReplace): self._sensitivity.add(replacement) if self._enclosed_for is not None: if self._enclosed_for.discard(toReplace): self._enclosed_for.add(replacement) @internal def _replace_child_statement(self, stm: "HdlStatement", replacement: List["HdlStatement"], update_io: bool) -> None: """ Replace a child statement with a list of other statements :attention: original statement is destroyed and entirely removed from circuit :note: sensitivity/endoints are actualized """ raise NotImplementedError( "This method should be implemented in child class", self.__class__, self)
def flattenTrees(root, nodeSelector: Callable[[LNode], bool], reversePortOrder): """ Walk all nodes and discover trees of nodes (usually operators) and reduce them to single node with multiple outputs :attention: selected nodes has to have single output and has to be connected to nets with single driver """ assert isinstance(root.children, list) for ch in root.children: if ch.children: flattenTrees(ch, nodeSelector, reversePortOrder) # collect all nodes which can be potentially reduced reducibleChildren = UniqList(ch for ch in root.children if nodeSelector(ch)) removedNodes = set() for _treeRoot in reducibleChildren: # try to pick a node from random tree and search it's root if _treeRoot in removedNodes: continue # we need to keep order of inputs, use pre-order treeRoot = searchRootOfTree(reducibleChildren, _treeRoot, removedNodes) reducedNodes, inputEdges = collectNodesInTree(treeRoot, reducibleChildren, removedNodes) if reversePortOrder: inputEdges = tuple(reversed(inputEdges)) # if tree is big enough for reduction, reduce it to single node if len(reducedNodes) > 1: assert inputEdges newNode = root.addNode(name=reducedNodes[0].name, cls=reducedNodes[0].cls) o = newNode.addPort("", PortType.OUTPUT, PortSide.EAST) oEdges = treeRoot.east[0].outgoingEdges # intented copy of oEdges for outputedge in list(oEdges): dsts = list(outputedge.dsts) assert len(dsts) > 0 outputedge.remove() root.addHyperEdge([ o, ], dsts, originObj=outputedge.originObj) port_names = [] bit_offset = 0 for i, (_, iP, iE) in enumerate(inputEdges): name = None index = len(inputEdges) - i - 1 origin_sig = iE.originObj if type(origin_sig) is tuple: for _origin_sig in origin_sig: if hasattr(_origin_sig, "_dtype"): origin_sig = _origin_sig if hasattr(origin_sig, "_dtype"): w = origin_sig._dtype.bit_length() if w > 1: name = "[%d:%d]" % (w + bit_offset, bit_offset) else: name = f"[{bit_offset:d}]" bit_offset += w if name is None: assert bit_offset == 0, ( "can not mix implicitly indexed and bit indexed array items", inputEdges) name = f"[{index:d}]" port_names.append(name) assert len(port_names) == len(inputEdges) for name, (_, iP, iE) in zip(port_names, inputEdges): inp = newNode.addPort(name, PortType.INPUT, PortSide.WEST) iE.removeTarget(iP) iE.addTarget(inp) _reducedNodes = set(reducedNodes) for n in reducedNodes: for e in tuple(n.iterEdges()): #raise AssertionError(n, "should be disconnected") for s in e.srcs: assert s.parentNode in _reducedNodes, (n, e, s) for d in e.dsts: assert d.parentNode in _reducedNodes, (n, e, d) e.remove() root.children.remove(n)
def _injectParametersIntoPortTypes( self, port_type_variants: List[Tuple[HdlPortItem, Dict[Tuple[Param, HValue], List[HdlType]]]], param_signals: List[RtlSignal]): updated_type_ids = set() param_sig_by_name = {p.name: p for p in param_signals} param_value_domain = {} for parent_port, param_val_to_t in port_type_variants: for (param, param_value), port_types in param_val_to_t.items(): param_value_domain.setdefault(param, set()).add(param_value) for parent_port, param_val_to_t in port_type_variants: if id(parent_port._dtype) in updated_type_ids: continue # check which unique parameter values affects the type of the port # if the type changes with any parameter value integrate it in to type of the port # print(parent_port, param_val_to_t) type_to_param_values = {} for (param, param_value), port_types in param_val_to_t.items(): for pt in port_types: cond = type_to_param_values.setdefault(pt, UniqList()) cond.append((param, param_value)) assert type_to_param_values, parent_port if len(type_to_param_values) == 1: continue # type does not change # Param: values params_used = {} for t, param_values in type_to_param_values.items(): for (param, param_val) in param_values: params_used.setdefault(param, set()).add(param_val) # filter out parameters which are not part of type specification process for p, p_vals in list(params_used.items()): if len(param_value_domain[p]) == len(p_vals): params_used.pop(p) # reset sets used to check parameter values for p, p_vals in params_used.items(): p_vals.clear() if not params_used: raise AssertionError( parent_port, "Type changes between the variants but it does not depend on parameter", param_val_to_t) if len(params_used) == 1 and list( params_used.keys())[0].get_hdl_type() == INT: # try to extract param * x + y p_val_to_port_w = {} for t, param_values in type_to_param_values.items(): for (param, param_val) in param_values: if param not in params_used: continue assert param_val not in p_val_to_port_w or p_val_to_port_w[ param_val] == t.bit_length(), parent_port p_val_to_port_w[param_val] = t.bit_length() # t_width = n*p + c _p_val_to_port_w = sorted(p_val_to_port_w.items()) t_width0, p0 = _p_val_to_port_w[0] t_width1, p1 = _p_val_to_port_w[1] # 0 == t_width0 - n*p0 + c # 0 == t_width1 - n*p1 + c # 0 == t_width0 - n*p0 - c + t_width1 - n*p1 - c # 0 == t_width0 + t_width1 - n*(p0 + p1) - 2c # c == (t_width0 + t_width1 - n*(p0 + p1) ) //2 # n has to be int, 0 < n <= t_width0/p0 # n is something like base size of port which is multipled by parameter # we searching n for which we can resolve c found_nc = None for n in range(1, t_width0 // p0 + 1): c = (t_width0 + t_width1 - n * (p0 + p1)) // 2 if t_width0 - n * p0 + c == 0 and t_width1 - n * p1 + c == 0: found_nc = (n, c) break if found_nc is None: raise NotImplementedError() else: p = list(params_used.keys())[0] p = param_sig_by_name[p._name] (n, c) = found_nc t = parent_port._dtype t._bit_length = INT.from_py(n) * p + c t._bit_length._const = True updated_type_ids.add(id(t)) else: condition_and_type_width = [] default_width = None for t, p_vals in sorted(type_to_param_values.items(), key=lambda x: x[0].bit_length()): cond = And(*(param_sig_by_name[p.hdl_name]._eq(p_val) for p, p_val in p_vals if p in params_used)) w = t.bit_length() if default_width is None: default_width = w condition_and_type_width.append((cond, w)) t = parent_port._dtype t._bit_length = reduce_ternary(condition_and_type_width, default_width) t._bit_length._const = True updated_type_ids.add(id(t))