From cdcc860967f89fe6abf3a3877a59cb6c23caba28 Mon Sep 17 00:00:00 2001 From: Oscar Gustafsson <oscar.gustafsson@gmail.com> Date: Tue, 16 May 2023 10:10:24 +0200 Subject: [PATCH] Add branch node in architecture and fix issue with multiple destinations --- b_asic/architecture.py | 75 +++++++++++++------ examples/folding_example_with_architecture.py | 23 +----- .../secondorderdirectformiir_architecture.py | 57 +++++++------- 3 files changed, 84 insertions(+), 71 deletions(-) diff --git a/b_asic/architecture.py b/b_asic/architecture.py index 40ff4cb2..8475b060 100644 --- a/b_asic/architecture.py +++ b/b_asic/architecture.py @@ -83,6 +83,13 @@ class HardwareBlock: def _repr_png_(self): return self._digraph()._repr_mimebundle_(include=["image/png"])["image/png"] + def _repr_svg_(self): + return self._digraph()._repr_mimebundle_(include=["image/svg+xml"])[ + "image/svg+xml" + ] + + _repr_html_ = _repr_svg_ + @property def entity_name(self) -> str: if self._entity_name is None: @@ -488,8 +495,12 @@ of :class:`~b_asic.architecture.ProcessingElement` ) self._memories = [memories] if isinstance(memories, Memory) else list(memories) self._direct_interconnects = direct_interconnects - self._variable_inport_to_resource: Dict[InputPort, Tuple[Resource, int]] = {} - self._variable_outport_to_resource: Dict[OutputPort, Tuple[Resource, int]] = {} + self._variable_inport_to_resource: DefaultDict[ + InputPort, Set[Tuple[Resource, int]] + ] = defaultdict(set) + self._variable_outport_to_resource: DefaultDict[ + OutputPort, Set[Tuple[Resource, int]] + ] = defaultdict(set) self._operation_inport_to_resource: Dict[InputPort, Resource] = {} self._operation_outport_to_resource: Dict[OutputPort, Resource] = {} @@ -513,10 +524,14 @@ of :class:`~b_asic.architecture.ProcessingElement` return schedule_times.pop() def _build_dicts(self) -> None: - self._variable_inport_to_resource: Dict[InputPort, Tuple[Resource, int]] = {} - self._variable_outport_to_resource: Dict[OutputPort, Tuple[Resource, int]] = {} - self._operation_inport_to_resource: Dict[InputPort, Resource] = {} - self._operation_outport_to_resource: Dict[OutputPort, Resource] = {} + self._variable_inport_to_resource: DefaultDict[ + InputPort, Set[Tuple[Resource, int]] + ] = defaultdict(set) + self._variable_outport_to_resource: DefaultDict[ + OutputPort, Set[Tuple[Resource, int]] + ] = defaultdict(set) + self._operation_inport_to_resource = {} + self._operation_outport_to_resource = {} for pe in self.processing_elements: for operator in pe.processes: for input_port in operator.operation.inputs: @@ -527,19 +542,25 @@ of :class:`~b_asic.architecture.ProcessingElement` for memory in self.memories: for mv in memory: for read_port in mv.read_ports: - self._variable_inport_to_resource[read_port] = (memory, 0) # Fix - self._variable_outport_to_resource[mv.write_port] = (memory, 0) # Fix + self._variable_inport_to_resource[read_port].add((memory, 0)) # Fix + self._variable_outport_to_resource[mv.write_port].add( + (memory, 0) + ) # Fix if self._direct_interconnects: for di in self._direct_interconnects: di = cast(MemoryVariable, di) for read_port in di.read_ports: - self._variable_inport_to_resource[read_port] = ( - self._operation_outport_to_resource[di.write_port], - di.write_port.index, + self._variable_inport_to_resource[read_port].add( + ( + self._operation_outport_to_resource[di.write_port], + di.write_port.index, + ) ) - self._variable_outport_to_resource[di.write_port] = ( - self._operation_inport_to_resource[read_port], - read_port.index, + self._variable_outport_to_resource[di.write_port].add( + ( + self._operation_inport_to_resource[read_port], + read_port.index, + ) ) def validate_ports(self) -> None: @@ -637,9 +658,11 @@ of :class:`~b_asic.architecture.ProcessingElement` for var in pe.collection: var = cast(OperatorProcess, var) for i, input_ in enumerate(var.operation.inputs): - d_in[i][self._variable_inport_to_resource[input_]] += 1 + for v in self._variable_inport_to_resource[input_]: + d_in[i][v] += 1 for i, output in enumerate(var.operation.outputs): - d_out[i][self._variable_outport_to_resource[output]] += 1 + for v in self._variable_outport_to_resource[output]: + d_out[i][v] += 1 return [dict(d) for d in d_in], [dict(d) for d in d_out] def resource_from_name(self, name: str): @@ -688,8 +711,8 @@ of :class:`~b_asic.architecture.ProcessingElement` raise KeyError(f"{proc} not in {re_from.entity_name}") self._build_dicts() - def _digraph(self) -> Digraph: - edges: Set[Tuple[str, str, str]] = set() + def _digraph(self, branch_node=True) -> Digraph: + edges: DefaultDict[str, Set[Tuple[str, str]]] = defaultdict(set) dg = Digraph(node_attr={'shape': 'record'}) for i, mem in enumerate(self._memories): dg.node(mem.entity_name, mem._struct_def()) @@ -699,24 +722,28 @@ of :class:`~b_asic.architecture.ProcessingElement` inputs, outputs = self.get_interconnects_for_pe(pe) for i, inp in enumerate(inputs): for (source, port), cnt in inp.items(): - edges.add( + edges[f"{source.entity_name}:out{port}"].add( ( - f"{source.entity_name}:out{port}", f"{pe.entity_name}:in{i}", f"{cnt}", ) ) for o, output in enumerate(outputs): for (dest, port), cnt in output.items(): - edges.add( + edges[f"{pe.entity_name}:out{o}"].add( ( - f"{pe.entity_name}:out{o}", f"{dest.entity_name}:in{port}", f"{cnt}", ) ) - for src_str, dest_str, cnt_str in edges: - dg.edge(src_str, dest_str, label=cnt_str) + for src_str, dest_cnts in edges.items(): + if len(dest_cnts) > 1 and branch_node: + branch = f"{src_str}_branch".replace(":", "") + dg.node(branch, shape='point') + dg.edge(src_str, branch) + src_str = branch + for dest_str, cnt_str in dest_cnts: + dg.edge(src_str, dest_str, label=cnt_str) return dg @property diff --git a/examples/folding_example_with_architecture.py b/examples/folding_example_with_architecture.py index 5d531dd8..85a7fc65 100644 --- a/examples/folding_example_with_architecture.py +++ b/examples/folding_example_with_architecture.py @@ -107,25 +107,4 @@ arch = Architecture({p1, p2, p_in, p_out}, memories, direct_interconnects=direct # %% # The architecture can be rendered in enriched shells. -# -# .. graphviz:: -# -# digraph { -# node [shape=record] -# memory0 [label="{{<in0> in0}|memory0|{<out0> out0}}"] -# memory1 [label="{{<in0> in0}|memory1|{<out0> out0}}"] -# output [label="{{<in0> in0}|output}"] -# adder [label="{{<in0> in0|<in1> in1}|adder|{<out0> out0}}"] -# input [label="{input|{<out0> out0}}"] -# cmul [label="{{<in0> in0}|cmul|{<out0> out0}}"] -# memory1:out0 -> adder:in1 [label=2] -# cmul:out0 -> adder:in0 [label=2] -# memory0:out0 -> cmul:in0 [label=3] -# adder:out0 -> output:in0 [label=1] -# input:out0 -> adder:in0 [label=1] -# adder:out0 -> memory0:in0 [label=1] -# adder:out0 -> adder:in1 [label=2] -# memory0:out0 -> adder:in0 [label=1] -# adder:out0 -> cmul:in0 [label=1] -# cmul:out0 -> memory1:in0 [label=2] -# } +arch diff --git a/examples/secondorderdirectformiir_architecture.py b/examples/secondorderdirectformiir_architecture.py index 82b50d2f..41b15843 100644 --- a/examples/secondorderdirectformiir_architecture.py +++ b/examples/secondorderdirectformiir_architecture.py @@ -91,28 +91,35 @@ arch = Architecture({p1, p2, p_in, p_out}, memories, direct_interconnects=direct # %% # The architecture can be rendered in enriched shells. -# -# .. graphviz:: -# -# digraph { -# node [shape=record] -# memory1 [label="{{<in0> in0}|memory1|{<out0> out0}}"] -# memory0 [label="{{<in0> in0}|memory0|{<out0> out0}}"] -# memory2 [label="{{<in0> in0}|memory2|{<out0> out0}}"] -# in [label="{in|{<out0> out0}}"] -# out [label="{{<in0> in0}|out}"] -# cmul [label="{{<in0> in0}|cmul|{<out0> out0}}"] -# adder [label="{{<in0> in0|<in1> in1}|adder|{<out0> out0}}"] -# memory1:out0 -> adder:in1 [label=1] -# cmul:out0 -> adder:in0 [label=1] -# cmul:out0 -> memory0:in0 [label=3] -# memory0:out0 -> adder:in0 [label=1] -# adder:out0 -> adder:in1 [label=1] -# memory1:out0 -> cmul:in0 [label=5] -# memory0:out0 -> adder:in1 [label=2] -# adder:out0 -> memory1:in0 [label=2] -# adder:out0 -> out:in0 [label=1] -# memory2:out0 -> adder:in0 [label=2] -# cmul:out0 -> memory2:in0 [label=2] -# in:out0 -> cmul:in0 [label=1] -# } +arch + +# %% +# To reduce the amount of interconnect, the ``cuml3.0`` variable can be moved from +# ``memory0`` to ``memory2``. In this way, ``memory0``only gets variables from the +# adder and an input multiplexer can be avoided. The memoried must be assigned again as +# the contents have changed. +arch.move_process('cmul3.0', 'memory0', 'memory2') +memories[0].assign() +memories[2].assign() + +memories[0].show_content("New assigned memory0") +memories[2].show_content("New assigned memory2") + +# %% +# Looking at the architecture it is clear that there is now only one input to +# ``memory0``, so no input multiplexer required. +arch + +# %% +# It is of course also possible to move ``add4.0`` to ``memory2`` to save one memory +# cell. +arch.move_process('add4.0', 'memory0', 'memory2') +memories[0].assign() +memories[2].assign() + +memories[0].show_content("New assigned memory0") +memories[2].show_content("New assigned memory2") + +# %% +# However, this comes at the expense of an additional input to ``memory2``. +arch -- GitLab