diff --git a/README.md b/README.md index fd98f919202588942a3e8d394b0b461ef63cfe54..20f28bee3cfd71fc1b03100b9f9f3632f8a8c284 100644 --- a/README.md +++ b/README.md @@ -8,17 +8,27 @@ How to build and debug the library during development. ### Prerequisites The following packages are required in order to build the library: -* cmake 3.8+ +* C++: + * cmake 3.8+ * gcc 7+/clang 7+/msvc 16+ * fmtlib 5.2.1+ * pybind11 2.3.0+ -* python 3.6+ +* Python: + * python 3.6+ * setuptools - * wheel * pybind11 * numpy * pyside2/pyqt5 +To build a binary distribution, the following additional packages are required: +* Python: + * wheel + +To run the test suite, the following additional packages are required: +* Python: + * pytest + * pytest-cov (for testing with coverage) + ### Using CMake directly How to build using CMake. diff --git a/b_asic/operation.py b/b_asic/operation.py index 75c023a3623387042daa6867ca21aeacec9afd4f..f8ac22e2a1d26e13365d0d742775de6f1f020057 100644 --- a/b_asic/operation.py +++ b/b_asic/operation.py @@ -350,7 +350,7 @@ class AbstractOperation(Operation, AbstractGraphComponent): # Import here to avoid circular imports. from b_asic.special_operations import Input try: - result = self.evaluate([Input()] * self.input_count) + result = self.evaluate(*([Input()] * self.input_count)) if isinstance(result, collections.Sequence) and all(isinstance(e, Operation) for e in result): return result if isinstance(result, Operation): diff --git a/b_asic/signal_flow_graph.py b/b_asic/signal_flow_graph.py index e5ad147e4836761a318888a7a0f93c32db120782..4dfb5ef34340a60b55234c2aa6be6de306b5a713 100644 --- a/b_asic/signal_flow_graph.py +++ b/b_asic/signal_flow_graph.py @@ -247,6 +247,40 @@ class SFG(AbstractOperation): results[self.key(index, prefix)] = value return value + def connect_external_signals_to_components(self) -> bool: + """ Connects any external signals to this SFG's internal operations. This SFG becomes unconnected to the SFG + it is a component off, causing it to become invalid afterwards. Returns True if succesful, False otherwise. """ + if len(self.inputs) != len(self.input_operations): + raise IndexError(f"Number of inputs does not match the number of input_operations in SFG.") + if len(self.outputs) != len(self.output_operations): + raise IndexError(f"Number of outputs does not match the number of output_operations SFG.") + if len(self.input_signals) == 0: + return False + if len(self.output_signals) == 0: + return False + + # For each input_signal, connect it to the corresponding operation + for port, input_operation in zip(self.inputs, self.input_operations): + dest = input_operation.output(0).signals[0].destination + dest.clear() + port.signals[0].set_destination(dest) + # For each output_signal, connect it to the corresponding operation + for port, output_operation in zip(self.outputs, self.output_operations): + src = output_operation.input(0).signals[0].source + src.clear() + port.signals[0].set_source(src) + return True + + @property + def input_operations(self) -> Sequence[Operation]: + """Get the internal input operations in the same order as their respective input ports.""" + return self._input_operations + + @property + def output_operations(self) -> Sequence[Operation]: + """Get the internal output operations in the same order as their respective output ports.""" + return self._output_operations + def split(self) -> Iterable[Operation]: return self.operations diff --git a/test/test_sfg.py b/test/test_sfg.py index f7aff28f9f1321bf9c06287967c0698389ddf629..cf309c263d65848ab31b4b00e78ebf99132b8ad7 100644 --- a/test/test_sfg.py +++ b/test/test_sfg.py @@ -1,6 +1,7 @@ import pytest -from b_asic import SFG, Signal, Input, Output, Constant, ConstantMultiplication, Addition, Multiplication, Register, Butterfly +from b_asic import SFG, Signal, Input, Output, Constant, ConstantMultiplication, Addition, Multiplication, Register, \ + Butterfly, Subtraction class TestInit: @@ -288,8 +289,6 @@ class TestFindComponentsWithTypeName: mac_sfg = SFG(inputs=[inp1, inp2], outputs=[out1], name="mac_sfg") - print(mac_sfg._components_by_id) - assert {comp.name for comp in mac_sfg.get_components_with_type_name( inp1.type_name())} == {"INP1", "INP2", "INP3"} @@ -470,3 +469,155 @@ class TestDepends: sfg_two_inputs_two_outputs_independent.inputs_required_for_output(0)) == {0} assert set( sfg_two_inputs_two_outputs_independent.inputs_required_for_output(1)) == {1} + + +class TestConnectExternalSignalsToComponentsSoloComp: + + def test_connect_external_signals_to_components_mac(self): + """ Replace a MAC with inner components in an SFG """ + inp1 = Input("INP1") + inp2 = Input("INP2") + inp3 = Input("INP3") + add1 = Addition(None, None, "ADD1") + add2 = Addition(None, None, "ADD2") + mul1 = Multiplication(None, None, "MUL1") + out1 = Output(None, "OUT1") + + add1.input(0).connect(inp1, "S1") + add1.input(1).connect(inp2, "S2") + add2.input(0).connect(add1, "S3") + add2.input(1).connect(inp3, "S4") + mul1.input(0).connect(add1, "S5") + mul1.input(1).connect(add2, "S6") + out1.input(0).connect(mul1, "S7") + + mac_sfg = SFG(inputs=[inp1, inp2], outputs=[out1]) + + inp4 = Input("INP4") + inp5 = Input("INP5") + out2 = Output(None, "OUT2") + + mac_sfg.input(0).connect(inp4, "S8") + mac_sfg.input(1).connect(inp5, "S9") + out2.input(0).connect(mac_sfg.outputs[0], "S10") + + test_sfg = SFG(inputs=[inp4, inp5], outputs=[out2]) + assert test_sfg.evaluate(1, 2) == 9 + mac_sfg.connect_external_signals_to_components() + assert test_sfg.evaluate(1, 2) == 9 + assert not test_sfg.connect_external_signals_to_components() + + def test_connect_external_signals_to_components_operation_tree(self, operation_tree): + """ Replaces an SFG with only a operation_tree component with its inner components """ + sfg1 = SFG(outputs=[Output(operation_tree)]) + out1 = Output(None, "OUT1") + out1.input(0).connect(sfg1.outputs[0], "S1") + test_sfg = SFG(outputs=[out1]) + assert test_sfg.evaluate_output(0, []) == 5 + sfg1.connect_external_signals_to_components() + assert test_sfg.evaluate_output(0, []) == 5 + assert not test_sfg.connect_external_signals_to_components() + + def test_connect_external_signals_to_components_large_operation_tree(self, large_operation_tree): + """ Replaces an SFG with only a large_operation_tree component with its inner components """ + sfg1 = SFG(outputs=[Output(large_operation_tree)]) + out1 = Output(None, "OUT1") + out1.input(0).connect(sfg1.outputs[0], "S1") + test_sfg = SFG(outputs=[out1]) + assert test_sfg.evaluate_output(0, []) == 14 + sfg1.connect_external_signals_to_components() + assert test_sfg.evaluate_output(0, []) == 14 + assert not test_sfg.connect_external_signals_to_components() + + +class TestConnectExternalSignalsToComponentsMultipleComp: + + def test_connect_external_signals_to_components_operation_tree(self, operation_tree): + """ Replaces a operation_tree in an SFG with other components """ + sfg1 = SFG(outputs=[Output(operation_tree)]) + + inp1 = Input("INP1") + inp2 = Input("INP2") + out1 = Output(None, "OUT1") + + add1 = Addition(None, None, "ADD1") + add2 = Addition(None, None, "ADD2") + + add1.input(0).connect(inp1, "S1") + add1.input(1).connect(inp2, "S2") + add2.input(0).connect(add1, "S3") + add2.input(1).connect(sfg1.outputs[0], "S4") + out1.input(0).connect(add2, "S5") + + test_sfg = SFG(inputs=[inp1, inp2], outputs=[out1]) + assert test_sfg.evaluate(1, 2) == 8 + sfg1.connect_external_signals_to_components() + assert test_sfg.evaluate(1, 2) == 8 + assert not test_sfg.connect_external_signals_to_components() + + def test_connect_external_signals_to_components_large_operation_tree(self, large_operation_tree): + """ Replaces a large_operation_tree in an SFG with other components """ + sfg1 = SFG(outputs=[Output(large_operation_tree)]) + + inp1 = Input("INP1") + inp2 = Input("INP2") + out1 = Output(None, "OUT1") + add1 = Addition(None, None, "ADD1") + add2 = Addition(None, None, "ADD2") + + add1.input(0).connect(inp1, "S1") + add1.input(1).connect(inp2, "S2") + add2.input(0).connect(add1, "S3") + add2.input(1).connect(sfg1.outputs[0], "S4") + out1.input(0).connect(add2, "S5") + + test_sfg = SFG(inputs=[inp1, inp2], outputs=[out1]) + assert test_sfg.evaluate(1, 2) == 17 + sfg1.connect_external_signals_to_components() + assert test_sfg.evaluate(1, 2) == 17 + assert not test_sfg.connect_external_signals_to_components() + + def create_sfg(self, op_tree): + """ Create a simple SFG with either operation_tree or large_operation_tree """ + sfg1 = SFG(outputs=[Output(op_tree)]) + + inp1 = Input("INP1") + inp2 = Input("INP2") + out1 = Output(None, "OUT1") + add1 = Addition(None, None, "ADD1") + add2 = Addition(None, None, "ADD2") + + add1.input(0).connect(inp1, "S1") + add1.input(1).connect(inp2, "S2") + add2.input(0).connect(add1, "S3") + add2.input(1).connect(sfg1.outputs[0], "S4") + out1.input(0).connect(add2, "S5") + + return SFG(inputs=[inp1, inp2], outputs=[out1]) + + def test_connect_external_signals_to_components_many_op(self, large_operation_tree): + """ Replaces an sfg component in a larger SFG with several component operations """ + inp1 = Input("INP1") + inp2 = Input("INP2") + inp3 = Input("INP3") + inp4 = Input("INP4") + out1 = Output(None, "OUT1") + add1 = Addition(None, None, "ADD1") + sub1 = Subtraction(None, None, "SUB1") + + add1.input(0).connect(inp1, "S1") + add1.input(1).connect(inp2, "S2") + + sfg1 = self.create_sfg(large_operation_tree) + + sfg1.input(0).connect(add1, "S3") + sfg1.input(1).connect(inp3, "S4") + sub1.input(0).connect(sfg1.outputs[0], "S5") + sub1.input(1).connect(inp4, "S6") + out1.input(0).connect(sub1, "S7") + + test_sfg = SFG(inputs=[inp1, inp2, inp3, inp4], outputs=[out1]) + assert test_sfg.evaluate(1, 2, 3, 4) == 16 + sfg1.connect_external_signals_to_components() + assert test_sfg.evaluate(1, 2, 3, 4) == 16 + assert not test_sfg.connect_external_signals_to_components()