diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000000000000000000000000000000000000..7548f76b9b80cc6c1725505ab0492be1d7e3317a --- /dev/null +++ b/.clang-format @@ -0,0 +1,151 @@ +AccessModifierOffset: -4 + +AlignAfterOpenBracket: DontAlign +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignConsecutiveMacros: true +AlignEscapedNewlines: DontAlign +AlignOperands: false +AlignTrailingComments: true + +AllowAllArgumentsOnNextLine: true +AllowAllConstructorInitializersOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: Empty +AllowShortCaseLabelsOnASingleLine: true +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: Inline +AllowShortLoopsOnASingleLine: false + +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: Yes + +BinPackArguments: false +BinPackParameters: true + +BreakBeforeBraces: Custom +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: true + +BreakBeforeBinaryOperators: None +BreakBeforeTernaryOperators: false +BreakConstructorInitializers: BeforeComma +BreakInheritanceList: BeforeComma +BreakStringLiterals: true + +ColumnLimit: 140 + +CommentPragmas: '' + +CompactNamespaces: false + +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 + +ContinuationIndentWidth: 4 + +Cpp11BracedListStyle: true + +DerivePointerAlignment: false + +DisableFormat: false + +FixNamespaceComments: true + +ForEachMacros: + - Q_FOREACH + - BOOST_FOREACH + - FOREACH + - FOR_EACH + +IncludeBlocks: Regroup +IncludeCategories: + - Regex: '^<' + Priority: 2 + - Regex: '.*' + Priority: 1 +IncludeIsMainRegex: '(_test)?$' + +IndentCaseLabels: true +IndentGotoLabels: false +IndentPPDirectives: None +IndentWidth: 4 +IndentWrappedFunctionNames: false + +KeepEmptyLinesAtTheStartOfBlocks: false + +Language: Cpp + +MacroBlockBegin: '' +MacroBlockEnd: '' + +MaxEmptyLinesToKeep: 1 + +NamespaceIndentation: None +NamespaceMacros: + - NAMESPACE + +PenaltyBreakAssignment: 100 +PenaltyBreakBeforeFirstCallParameter: 10 +PenaltyBreakComment: 10 +PenaltyBreakFirstLessLess: 100 +PenaltyBreakString: 10 +PenaltyBreakTemplateDeclaration: 10000 +PenaltyExcessCharacter: 999999 +PenaltyReturnTypeOnItsOwnLine: 10000 + +PointerAlignment: Left + +ReflowComments: false + +SortIncludes: true +SortUsingDeclarations: true + +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInCStyleCastParentheses: false +SpacesInContainerLiterals: false +SpacesInParentheses: false +SpacesInSquareBrackets: false + +Standard: Cpp11 + +StatementMacros: + - Q_UNUSED + +TabWidth: 4 + +TypenameMacros: + - STACK_OF + - LIST + - LIST_ENTRY + +UseTab: ForContinuationAndIndentation \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..0bdf5476787722ba2007a85c8c73255d56178262 --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +.vs/ +.vscode/ +build*/ +bin*/ +logs/ +dist/ +CMakeLists.txt.user* +*.autosave +*.creator +*.creator.user* +\#*\# +/.emacs.desktop +/.emacs.desktop.lock +*.elc +auto-save-list +tramp +.\#* +*~ +.fuse_hudden* +.directory +.Trash-* +.nfs* +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db +$RECYCLE.BIN/ +*.stackdump +[Dd]esktop.ini +*.egg-info +__pycache__/ +env/ +venv/ \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6c51653c1a03a663601c9d49afbcd898f7fc86b9..3b2095bbac834a11ff4b8d3d5a13ad0f9903a090 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,10 +1,16 @@ +image: python:3.6 + stages: - - build + - test + +before_script: + - apt-get update --yes + - apt-get install --yes build-essential cmake libfmt-dev pybind11-dev + - pip3 install pytest pytest-cov + - pip3 install . + - pip3 show b_asic -PythonBuild: - stage: build - artifacts: - untracked: true +run tests: + stage: test script: - - apt-get update && apt-get install python3 -y - - bash build.sh \ No newline at end of file + - pytest test \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..3471fb4f473b069f9a4f77efec5d7aeb0fc84a79 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,79 @@ +cmake_minimum_required(VERSION 3.8) + +project( + "B-ASIC" + VERSION 0.0.1 + DESCRIPTION "Better ASIC Toolbox for python3" + LANGUAGES C CXX +) + +find_package(fmt 5.2.1 REQUIRED) +find_package(pybind11 CONFIG REQUIRED) + +set(LIBRARY_NAME "b_asic") +set(TARGET_NAME "_${LIBRARY_NAME}") + +if(NOT CMAKE_LIBRARY_OUTPUT_DIRECTORY) + include(GNUInstallDirs) + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_INSTALL_LIBDIR}") +endif() +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}") +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}") +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}") +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_DEBUG "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}") +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}") +set(CMAKE_PDB_OUTPUT_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}") +set(CMAKE_PDB_OUTPUT_DIRECTORY_DEBUG "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}") +set(CMAKE_PDB_OUTPUT_DIRECTORY_RELEASE "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}") +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}") +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}") +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}") + +pybind11_add_module( + "${TARGET_NAME}" + "${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp" +) + +target_include_directories( + "${TARGET_NAME}" + PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/src" +) + +target_compile_features( + "${TARGET_NAME}" + PRIVATE + cxx_std_17 +) +target_compile_options( + "${TARGET_NAME}" + PRIVATE + $<$<OR:$<CXX_COMPILER_ID:GNU>,$<CXX_COMPILER_ID:Clang>>: + -W -Wall -Wextra -Werror -Wno-psabi + $<$<CONFIG:Debug>:-g> + $<$<NOT:$<CONFIG:Debug>>:-O3> + > + $<$<CXX_COMPILER_ID:MSVC>: + /W3 /WX /permissive- /utf-8 + $<$<CONFIG:Debug>:/Od> + $<$<NOT:$<CONFIG:Debug>>:/Ot> + > +) + +target_link_libraries( + "${TARGET_NAME}" + PRIVATE + fmt::fmt +) + +add_custom_target( + remove_old_python_dir ALL + COMMAND ${CMAKE_COMMAND} -E remove_directory "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/${LIBRARY_NAME}" + COMMENT "Removing old python directory ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/${LIBRARY_NAME}" +) +add_custom_target( + copy_python_dir ALL + COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_CURRENT_LIST_DIR}/${LIBRARY_NAME}" "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/${LIBRARY_NAME}" + COMMENT "Copying python files to ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/${LIBRARY_NAME}" + DEPENDS "${TARGET_NAME}" remove_old_python_dir +) \ No newline at end of file diff --git a/LICENSE b/LICENSE index 17010bd5c1e2eb344384a5fa1f4fa6bfc4b72e63..669ce41e6f7c3cc001e8f9bb88a86c0dfaf755d9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020 TDDD96 +Copyright (c) 2020 TDDD96 PUM4 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000000000000000000000000000000000000..ce996f6c4ffcc6a70d095c24ed296ec3b69e5f43 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,4 @@ +include README.md +include LICENSE +include CMakeLists.txt +recursive-include src *.cpp *.h diff --git a/README.md b/README.md index 20c8c4a23579933ca86a74fade71c015da9a79cf..fd98f919202588942a3e8d394b0b461ef63cfe54 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,119 @@ -<img src="https://files.slack.com/files-pri/TSHPRJY83-FTTRW9MQ8/b-asic-logo-opaque.png" width="318" height="100"> -<br> -<h3>B-ASIC is an ASIC toolbox for Python 3 that simplifies circuit design and optimization.<h3> \ No newline at end of file +<img src="logo.png" width="278" height="100"> + +# B-ASIC - Better ASIC Toolbox +B-ASIC is an ASIC toolbox for Python 3 that simplifies circuit design and optimization. + +## Development +How to build and debug the library during development. + +### Prerequisites +The following packages are required in order to build the library: +* cmake 3.8+ + * gcc 7+/clang 7+/msvc 16+ + * fmtlib 5.2.1+ + * pybind11 2.3.0+ +* python 3.6+ + * setuptools + * wheel + * pybind11 + * numpy + * pyside2/pyqt5 + +### Using CMake directly +How to build using CMake. + +#### Configuring +In `B-ASIC`: +``` +mkdir build +cd build +cmake .. +``` + +#### Building (Debug) +In `B-ASIC/build`: +``` +cmake --build . +``` +The output gets written to `B-ASIC/build/lib`. + +#### Building (Release) +In `B-ASIC/build`: +``` +cmake --build . --config Release +``` +The output gets written to `B-ASIC/build/lib`. + +### Using setuptools to create a package +How to create a package using setuptools that can be installed using pip. + +#### Setup (Binary distribution) +In `B-ASIC`: +``` +python3 setup.py bdist_wheel +``` +The output gets written to `B-ASIC/dist/b_asic-<version>-<python_tag>-<abi_tag>-<platform_tag>.whl`. + +#### Setup (Source distribution) +In `B-ASIC`: +``` +python3 setup.py sdist +``` +The output gets written to `B-ASIC/dist/b-asic-<version>.tar.gz`. + +#### Installation (Binary distribution) +In `B-ASIC/dist`: +``` +pip install b_asic-<version>-<python_tag>-<abi_tag>-<platform_tag>.whl +``` + +#### Installation (Source distribution) +In `B-ASIC/dist`: +``` +pip install b-asic-<version>.tar.gz +``` + +### Running tests +How to run the tests using pytest in a virtual environment. + +#### Linux/OS X +In `B-ASIC`: +``` +python3 -m venv env +source env/bin/activate +pip install . +pytest +``` + +#### Windows +In `B-ASIC` (as admin): +``` +python3 -m venv env +.\env\Scripts\activate.bat +pip install . +pytest +``` + +#### Test with coverage +``` +pytest --cov=b_asic --cov-report html test +``` + +## Usage +How to build and use the library as a user. + +### Installation +``` +pip install b_asic +``` + +### Importing +``` +python3 +>>> import b_asic as asic +>>> help(asic) +``` + +## License +B-ASIC is distributed under the MIT license. +See the included LICENSE file for more information. diff --git a/b_asic/__init__.py b/b_asic/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..7e40ad52cdc51da3e1d91964ad55cfc90e12a34a --- /dev/null +++ b/b_asic/__init__.py @@ -0,0 +1,14 @@ +""" +Better ASIC Toolbox. +TODO: More info. +""" +from b_asic.core_operations import * +from b_asic.graph_component import * +from b_asic.graph_id import * +from b_asic.operation import * +from b_asic.precedence_chart import * +from b_asic.port import * +from b_asic.schema import * +from b_asic.signal_flow_graph import * +from b_asic.signal import * +from b_asic.simulation import * diff --git a/b_asic/core_operations.py b/b_asic/core_operations.py new file mode 100644 index 0000000000000000000000000000000000000000..8902b169c07e600a843e526d44452fb9386ff7a9 --- /dev/null +++ b/b_asic/core_operations.py @@ -0,0 +1,337 @@ +"""@package docstring +B-ASIC Core Operations Module. +TODO: More info. +""" + +from numbers import Number +from typing import Any +from numpy import conjugate, sqrt, abs as np_abs +from b_asic.port import InputPort, OutputPort +from b_asic.graph_id import GraphIDType +from b_asic.operation import AbstractOperation +from b_asic.graph_component import Name, TypeName + + +class Input(AbstractOperation): + """Input operation. + TODO: More info. + """ + + # TODO: Implement all functions. + + @property + def type_name(self) -> TypeName: + return "in" + + +class Constant(AbstractOperation): + """Constant value operation. + TODO: More info. + """ + + def __init__(self, value: Number = 0, name: Name = ""): + super().__init__(name) + + self._output_ports = [OutputPort(0, self)] + self._parameters["value"] = value + + def evaluate(self): + return self.param("value") + + @property + def type_name(self) -> TypeName: + return "c" + + +class Addition(AbstractOperation): + """Binary addition operation. + TODO: More info. + """ + + def __init__(self, source1: OutputPort = None, source2: OutputPort = None, name: Name = ""): + super().__init__(name) + + self._input_ports = [InputPort(0, self), InputPort(1, self)] + self._output_ports = [OutputPort(0, self)] + + if source1 is not None: + self._input_ports[0].connect(source1) + if source2 is not None: + self._input_ports[1].connect(source2) + + def evaluate(self, a, b): + return a + b + + @property + def type_name(self) -> TypeName: + return "add" + + +class Subtraction(AbstractOperation): + """Binary subtraction operation. + TODO: More info. + """ + + def __init__(self, source1: OutputPort = None, source2: OutputPort = None, name: Name = ""): + super().__init__(name) + self._input_ports = [InputPort(0, self), InputPort(1, self)] + self._output_ports = [OutputPort(0, self)] + + if source1 is not None: + self._input_ports[0].connect(source1) + if source2 is not None: + self._input_ports[1].connect(source2) + + def evaluate(self, a, b): + return a - b + + @property + def type_name(self) -> TypeName: + return "sub" + + +class Multiplication(AbstractOperation): + """Binary multiplication operation. + TODO: More info. + """ + + def __init__(self, source1: OutputPort = None, source2: OutputPort = None, name: Name = ""): + super().__init__(name) + self._input_ports = [InputPort(0, self), InputPort(1, self)] + self._output_ports = [OutputPort(0, self)] + + if source1 is not None: + self._input_ports[0].connect(source1) + if source2 is not None: + self._input_ports[1].connect(source2) + + def evaluate(self, a, b): + return a * b + + @property + def type_name(self) -> TypeName: + return "mul" + + +class Division(AbstractOperation): + """Binary division operation. + TODO: More info. + """ + + def __init__(self, source1: OutputPort = None, source2: OutputPort = None, name: Name = ""): + super().__init__(name) + self._input_ports = [InputPort(0, self), InputPort(1, self)] + self._output_ports = [OutputPort(0, self)] + + if source1 is not None: + self._input_ports[0].connect(source1) + if source2 is not None: + self._input_ports[1].connect(source2) + + def evaluate(self, a, b): + return a / b + + @property + def type_name(self) -> TypeName: + return "div" + + +class SquareRoot(AbstractOperation): + """Unary square root operation. + TODO: More info. + """ + + def __init__(self, source1: OutputPort = None, name: Name = ""): + super().__init__(name) + self._input_ports = [InputPort(0, self)] + self._output_ports = [OutputPort(0, self)] + + if source1 is not None: + self._input_ports[0].connect(source1) + + def evaluate(self, a): + return sqrt((complex)(a)) + + @property + def type_name(self) -> TypeName: + return "sqrt" + + +class ComplexConjugate(AbstractOperation): + """Unary complex conjugate operation. + TODO: More info. + """ + + def __init__(self, source1: OutputPort = None, name: Name = ""): + super().__init__(name) + self._input_ports = [InputPort(0, self)] + self._output_ports = [OutputPort(0, self)] + + if source1 is not None: + self._input_ports[0].connect(source1) + + def evaluate(self, a): + return conjugate(a) + + @property + def type_name(self) -> TypeName: + return "conj" + + +class Max(AbstractOperation): + """Binary max operation. + TODO: More info. + """ + + def __init__(self, source1: OutputPort = None, source2: OutputPort = None, name: Name = ""): + super().__init__(name) + self._input_ports = [InputPort(0, self), InputPort(1, self)] + self._output_ports = [OutputPort(0, self)] + + if source1 is not None: + self._input_ports[0].connect(source1) + if source2 is not None: + self._input_ports[1].connect(source2) + + def evaluate(self, a, b): + assert not isinstance(a, complex) and not isinstance(b, complex), \ + ("core_operations.Max does not support complex numbers.") + return a if a > b else b + + @property + def type_name(self) -> TypeName: + return "max" + + +class Min(AbstractOperation): + """Binary min operation. + TODO: More info. + """ + + def __init__(self, source1: OutputPort = None, source2: OutputPort = None, name: Name = ""): + super().__init__(name) + self._input_ports = [InputPort(0, self), InputPort(1, self)] + self._output_ports = [OutputPort(0, self)] + + if source1 is not None: + self._input_ports[0].connect(source1) + if source2 is not None: + self._input_ports[1].connect(source2) + + def evaluate(self, a, b): + assert not isinstance(a, complex) and not isinstance(b, complex), \ + ("core_operations.Min does not support complex numbers.") + return a if a < b else b + + @property + def type_name(self) -> TypeName: + return "min" + + +class Absolute(AbstractOperation): + """Unary absolute value operation. + TODO: More info. + """ + + def __init__(self, source1: OutputPort = None, name: Name = ""): + super().__init__(name) + self._input_ports = [InputPort(0, self)] + self._output_ports = [OutputPort(0, self)] + + if source1 is not None: + self._input_ports[0].connect(source1) + + def evaluate(self, a): + return np_abs(a) + + @property + def type_name(self) -> TypeName: + return "abs" + + +class ConstantMultiplication(AbstractOperation): + """Unary constant multiplication operation. + TODO: More info. + """ + + def __init__(self, coefficient: Number, source1: OutputPort = None, name: Name = ""): + super().__init__(name) + self._input_ports = [InputPort(0, self)] + self._output_ports = [OutputPort(0, self)] + self._parameters["coefficient"] = coefficient + + if source1 is not None: + self._input_ports[0].connect(source1) + + def evaluate(self, a): + return a * self.param("coefficient") + + @property + def type_name(self) -> TypeName: + return "cmul" + + +class ConstantAddition(AbstractOperation): + """Unary constant addition operation. + TODO: More info. + """ + + def __init__(self, coefficient: Number, source1: OutputPort = None, name: Name = ""): + super().__init__(name) + self._input_ports = [InputPort(0, self)] + self._output_ports = [OutputPort(0, self)] + self._parameters["coefficient"] = coefficient + + if source1 is not None: + self._input_ports[0].connect(source1) + + def evaluate(self, a): + return a + self.param("coefficient") + + @property + def type_name(self) -> TypeName: + return "cadd" + + +class ConstantSubtraction(AbstractOperation): + """Unary constant subtraction operation. + TODO: More info. + """ + + def __init__(self, coefficient: Number, source1: OutputPort = None, name: Name = ""): + super().__init__(name) + self._input_ports = [InputPort(0, self)] + self._output_ports = [OutputPort(0, self)] + self._parameters["coefficient"] = coefficient + + if source1 is not None: + self._input_ports[0].connect(source1) + + def evaluate(self, a): + return a - self.param("coefficient") + + @property + def type_name(self) -> TypeName: + return "csub" + + +class ConstantDivision(AbstractOperation): + """Unary constant division operation. + TODO: More info. + """ + + def __init__(self, coefficient: Number, source1: OutputPort = None, name: Name = ""): + super().__init__(name) + self._input_ports = [InputPort(0, self)] + self._output_ports = [OutputPort(0, self)] + self._parameters["coefficient"] = coefficient + + if source1 is not None: + self._input_ports[0].connect(source1) + + def evaluate(self, a): + return a / self.param("coefficient") + + @property + def type_name(self) -> TypeName: + return "cdiv" diff --git a/b_asic/graph_component.py b/b_asic/graph_component.py new file mode 100644 index 0000000000000000000000000000000000000000..1987d4491e12089fec401eedc14fb91a7274252e --- /dev/null +++ b/b_asic/graph_component.py @@ -0,0 +1,54 @@ +"""@package docstring +B-ASIC Operation Module. +TODO: More info. +""" + +from abc import ABC, abstractmethod +from typing import NewType + +Name = NewType("Name", str) +TypeName = NewType("TypeName", str) + + +class GraphComponent(ABC): + """Graph component interface. + TODO: More info. + """ + + @property + @abstractmethod + def type_name(self) -> TypeName: + """Return the type name of the graph component""" + raise NotImplementedError + + @property + @abstractmethod + def name(self) -> Name: + """Return the name of the graph component.""" + raise NotImplementedError + + @name.setter + @abstractmethod + def name(self, name: Name) -> None: + """Set the name of the graph component to the entered name.""" + raise NotImplementedError + + +class AbstractGraphComponent(GraphComponent): + """Abstract Graph Component class which is a component of a signal flow graph. + + TODO: More info. + """ + + _name: Name + + def __init__(self, name: Name = ""): + self._name = name + + @property + def name(self) -> Name: + return self._name + + @name.setter + def name(self, name: Name) -> None: + self._name = name diff --git a/b_asic/graph_id.py b/b_asic/graph_id.py new file mode 100644 index 0000000000000000000000000000000000000000..8da6a9d4af6a1bee25125904527a2fd3a374ab90 --- /dev/null +++ b/b_asic/graph_id.py @@ -0,0 +1,26 @@ +"""@package docstring +B-ASIC Graph ID module for handling IDs of different objects in a graph. +TODO: More info +""" + +from collections import defaultdict +from typing import NewType, DefaultDict + +GraphID = NewType("GraphID", str) +GraphIDType = NewType("GraphIDType", str) +GraphIDNumber = NewType("GraphIDNumber", int) + + +class GraphIDGenerator: + """A class that generates Graph IDs for objects.""" + + _next_id_number: DefaultDict[GraphIDType, GraphIDNumber] + + def __init__(self): + self._next_id_number = defaultdict(lambda: 1) # Initalises every key element to 1 + + def get_next_id(self, graph_id_type: GraphIDType) -> GraphID: + """Return the next graph id for a certain graph id type.""" + graph_id = graph_id_type + str(self._next_id_number[graph_id_type]) + self._next_id_number[graph_id_type] += 1 # Increase the current id number + return graph_id diff --git a/b_asic/operation.py b/b_asic/operation.py new file mode 100644 index 0000000000000000000000000000000000000000..5578e3c48edcf15594d6d1cd71e71a17521eca25 --- /dev/null +++ b/b_asic/operation.py @@ -0,0 +1,268 @@ +"""@package docstring +B-ASIC Operation Module. +TODO: More info. +""" + +from abc import abstractmethod +from numbers import Number +from typing import List, Dict, Optional, Any, Set, TYPE_CHECKING +from collections import deque + +from b_asic.graph_component import GraphComponent, AbstractGraphComponent, Name +from b_asic.simulation import SimulationState, OperationState +from b_asic.signal import Signal + +if TYPE_CHECKING: + from b_asic.port import InputPort, OutputPort + + +class Operation(GraphComponent): + """Operation interface. + TODO: More info. + """ + + @abstractmethod + def inputs(self) -> "List[InputPort]": + """Get a list of all input ports.""" + raise NotImplementedError + + @abstractmethod + def outputs(self) -> "List[OutputPort]": + """Get a list of all output ports.""" + raise NotImplementedError + + @abstractmethod + def input_count(self) -> int: + """Get the number of input ports.""" + raise NotImplementedError + + @abstractmethod + def output_count(self) -> int: + """Get the number of output ports.""" + raise NotImplementedError + + @abstractmethod + def input(self, i: int) -> "InputPort": + """Get the input port at index i.""" + raise NotImplementedError + + @abstractmethod + def output(self, i: int) -> "OutputPort": + """Get the output port at index i.""" + raise NotImplementedError + + @abstractmethod + def params(self) -> Dict[str, Optional[Any]]: + """Get a dictionary of all parameter values.""" + raise NotImplementedError + + @abstractmethod + def param(self, name: str) -> Optional[Any]: + """Get the value of a parameter. + Returns None if the parameter is not defined. + """ + raise NotImplementedError + + @abstractmethod + def set_param(self, name: str, value: Any) -> None: + """Set the value of a parameter. + The parameter must be defined. + """ + raise NotImplementedError + + @abstractmethod + def evaluate_outputs(self, state: "SimulationState") -> List[Number]: + """Simulate the circuit until its iteration count matches that of the simulation state, + then return the resulting output vector. + """ + raise NotImplementedError + + @abstractmethod + def split(self) -> "List[Operation]": + """Split the operation into multiple operations. + If splitting is not possible, this may return a list containing only the operation itself. + """ + raise NotImplementedError + + @property + @abstractmethod + def neighbors(self) -> "List[Operation]": + """Return all operations that are connected by signals to this operation. + If no neighbors are found, this returns an empty list. + """ + raise NotImplementedError + + +class AbstractOperation(Operation, AbstractGraphComponent): + """Generic abstract operation class which most implementations will derive from. + TODO: More info. + """ + + _input_ports: List["InputPort"] + _output_ports: List["OutputPort"] + _parameters: Dict[str, Optional[Any]] + + def __init__(self, name: Name = ""): + super().__init__(name) + self._input_ports = [] + self._output_ports = [] + self._parameters = {} + + @abstractmethod + def evaluate(self, *inputs) -> Any: # pylint: disable=arguments-differ + """Evaluate the operation and generate a list of output values given a + list of input values. + """ + raise NotImplementedError + + def inputs(self) -> List["InputPort"]: + return self._input_ports.copy() + + def outputs(self) -> List["OutputPort"]: + return self._output_ports.copy() + + def input_count(self) -> int: + return len(self._input_ports) + + def output_count(self) -> int: + return len(self._output_ports) + + def input(self, i: int) -> "InputPort": + return self._input_ports[i] + + def output(self, i: int) -> "OutputPort": + return self._output_ports[i] + + def params(self) -> Dict[str, Optional[Any]]: + return self._parameters.copy() + + def param(self, name: str) -> Optional[Any]: + return self._parameters.get(name) + + def set_param(self, name: str, value: Any) -> None: + assert name in self._parameters # TODO: Error message. + self._parameters[name] = value + + def evaluate_outputs(self, state: SimulationState) -> List[Number]: + # TODO: Check implementation. + input_count: int = self.input_count() + output_count: int = self.output_count() + assert input_count == len(self._input_ports) # TODO: Error message. + assert output_count == len(self._output_ports) # TODO: Error message. + + self_state: OperationState = state.operation_states[self] + + while self_state.iteration < state.iteration: + input_values: List[Number] = [0] * input_count + for i in range(input_count): + source: Signal = self._input_ports[i].signal + input_values[i] = source.operation.evaluate_outputs(state)[ + source.port_index] + + self_state.output_values = self.evaluate(input_values) + # TODO: Error message. + assert len(self_state.output_values) == output_count + self_state.iteration += 1 + for i in range(output_count): + for signal in self._output_ports[i].signals(): + destination: Signal = signal.destination + destination.evaluate_outputs(state) + + return self_state.output_values + + def split(self) -> List[Operation]: + # TODO: Check implementation. + results = self.evaluate(self._input_ports) + if all(isinstance(e, Operation) for e in results): + return results + return [self] + + @property + def neighbors(self) -> List[Operation]: + neighbors: List[Operation] = [] + for port in self._input_ports: + for signal in port.signals: + neighbors.append(signal.source.operation) + + for port in self._output_ports: + for signal in port.signals: + neighbors.append(signal.destination.operation) + + return neighbors + + def traverse(self) -> Operation: + """Traverse the operation tree and return a generator with start point in the operation.""" + return self._breadth_first_search() + + def _breadth_first_search(self) -> Operation: + """Use breadth first search to traverse the operation tree.""" + visited: Set[Operation] = {self} + queue = deque([self]) + while queue: + operation = queue.popleft() + yield operation + for n_operation in operation.neighbors: + if n_operation not in visited: + visited.add(n_operation) + queue.append(n_operation) + + def __add__(self, other): + """Overloads the addition operator to make it return a new Addition operation + object that is connected to the self and other objects. If other is a number then + returns a ConstantAddition operation object instead. + """ + # Import here to avoid circular imports. + from b_asic.core_operations import Addition, ConstantAddition + + if isinstance(other, Operation): + return Addition(self.output(0), other.output(0)) + elif isinstance(other, Number): + return ConstantAddition(other, self.output(0)) + else: + raise TypeError("Other type is not an Operation or a Number.") + + def __sub__(self, other): + """Overloads the subtraction operator to make it return a new Subtraction operation + object that is connected to the self and other objects. If other is a number then + returns a ConstantSubtraction operation object instead. + """ + # Import here to avoid circular imports. + from b_asic.core_operations import Subtraction, ConstantSubtraction + + if isinstance(other, Operation): + return Subtraction(self.output(0), other.output(0)) + elif isinstance(other, Number): + return ConstantSubtraction(other, self.output(0)) + else: + raise TypeError("Other type is not an Operation or a Number.") + + def __mul__(self, other): + """Overloads the multiplication operator to make it return a new Multiplication operation + object that is connected to the self and other objects. If other is a number then + returns a ConstantMultiplication operation object instead. + """ + # Import here to avoid circular imports. + from b_asic.core_operations import Multiplication, ConstantMultiplication + + if isinstance(other, Operation): + return Multiplication(self.output(0), other.output(0)) + elif isinstance(other, Number): + return ConstantMultiplication(other, self.output(0)) + else: + raise TypeError("Other type is not an Operation or a Number.") + + def __truediv__(self, other): + """Overloads the division operator to make it return a new Division operation + object that is connected to the self and other objects. If other is a number then + returns a ConstantDivision operation object instead. + """ + # Import here to avoid circular imports. + from b_asic.core_operations import Division, ConstantDivision + + if isinstance(other, Operation): + return Division(self.output(0), other.output(0)) + elif isinstance(other, Number): + return ConstantDivision(other, self.output(0)) + else: + raise TypeError("Other type is not an Operation or a Number.") + diff --git a/b_asic/port.py b/b_asic/port.py new file mode 100644 index 0000000000000000000000000000000000000000..c22053df1928cf2f6ea32c4880816d551b85836a --- /dev/null +++ b/b_asic/port.py @@ -0,0 +1,223 @@ +"""@package docstring +B-ASIC Port Module. +TODO: More info. +""" + +from abc import ABC, abstractmethod +from typing import NewType, Optional, List + +from b_asic.operation import Operation +from b_asic.signal import Signal + +PortIndex = NewType("PortIndex", int) + +class Port(ABC): + """Port Interface. + + TODO: More documentaiton? + """ + + @property + @abstractmethod + def operation(self) -> Operation: + """Return the connected operation.""" + raise NotImplementedError + + @property + @abstractmethod + def index(self) -> PortIndex: + """Return the unique PortIndex.""" + raise NotImplementedError + + @property + @abstractmethod + def signals(self) -> List[Signal]: + """Return a list of all connected signals.""" + raise NotImplementedError + + @abstractmethod + def signal(self, i: int = 0) -> Signal: + """Return the connected signal at index i. + + Keyword argumens: + i: integer index of the signal requsted. + """ + raise NotImplementedError + + @property + @abstractmethod + def connected_ports(self) -> List["Port"]: + """Return a list of all connected Ports.""" + raise NotImplementedError + + @abstractmethod + def signal_count(self) -> int: + """Return the number of connected signals.""" + raise NotImplementedError + + @abstractmethod + def connect(self, port: "Port") -> Signal: + """Create and return a signal that is connected to this port and the entered + port and connect this port to the signal and the entered port to the signal.""" + raise NotImplementedError + + @abstractmethod + def add_signal(self, signal: Signal) -> None: + """Connect this port to the entered signal. If the entered signal isn't connected to + this port then connect the entered signal to the port aswell.""" + raise NotImplementedError + + @abstractmethod + def disconnect(self, port: "Port") -> None: + """Disconnect the entered port from the port by removing it from the ports signal. + If the entered port is still connected to this ports signal then disconnect the entered + port from the signal aswell.""" + raise NotImplementedError + + @abstractmethod + def remove_signal(self, signal: Signal) -> None: + """Remove the signal that was entered from the Ports signals. + If the entered signal still is connected to this port then disconnect the + entered signal from the port aswell. + + Keyword arguments: + - signal: Signal to remove. + """ + raise NotImplementedError + + @abstractmethod + def clear(self) -> None: + """Removes all connected signals from the Port.""" + raise NotImplementedError + + +class AbstractPort(Port): + """Abstract port class. + + Handles functionality for port id and saves the connection to the parent operation. + """ + + _index: int + _operation: Operation + + def __init__(self, index: int, operation: Operation): + self._index = index + self._operation = operation + + @property + def operation(self) -> Operation: + return self._operation + + @property + def index(self) -> PortIndex: + return self._index + + +class InputPort(AbstractPort): + """Input port. + TODO: More info. + """ + + _source_signal: Optional[Signal] + + def __init__(self, port_id: PortIndex, operation: Operation): + super().__init__(port_id, operation) + self._source_signal = None + + @property + def signals(self) -> List[Signal]: + return [] if self._source_signal is None else [self._source_signal] + + def signal(self, i: int = 0) -> Signal: + assert 0 <= i < self.signal_count(), "Signal index out of bound." + assert self._source_signal is not None, "No Signal connect to InputPort." + return self._source_signal + + @property + def connected_ports(self) -> List[Port]: + return [] if self._source_signal is None or self._source_signal.source is None \ + else [self._source_signal.source] + + def signal_count(self) -> int: + return 0 if self._source_signal is None else 1 + + def connect(self, port: "OutputPort") -> Signal: + assert self._source_signal is None, "Connecting new port to already connected input port." + return Signal(port, self) # self._source_signal is set by the signal constructor + + def add_signal(self, signal: Signal) -> None: + assert self._source_signal is None, "Connecting new port to already connected input port." + self._source_signal: Signal = signal + if self is not signal.destination: + # Connect this inputport as destination for this signal if it isn't already. + signal.set_destination(self) + + def disconnect(self, port: "OutputPort") -> None: + assert self._source_signal.source is port, "The entered port is not connected to this port." + self._source_signal.remove_source() + + def remove_signal(self, signal: Signal) -> None: + old_signal: Signal = self._source_signal + self._source_signal = None + if self is old_signal.destination: + # Disconnect the dest of the signal if this inputport currently is the dest + old_signal.remove_destination() + + def clear(self) -> None: + self.remove_signal(self._source_signal) + +class OutputPort(AbstractPort): + """Output port. + TODO: More info. + """ + + _destination_signals: List[Signal] + + def __init__(self, port_id: PortIndex, operation: Operation): + super().__init__(port_id, operation) + self._destination_signals = [] + + @property + def signals(self) -> List[Signal]: + return self._destination_signals.copy() + + def signal(self, i: int = 0) -> Signal: + assert 0 <= i < self.signal_count(), "Signal index out of bounds." + return self._destination_signals[i] + + @property + def connected_ports(self) -> List[Port]: + return [signal.destination for signal in self._destination_signals \ + if signal.destination is not None] + + def signal_count(self) -> int: + return len(self._destination_signals) + + def connect(self, port: InputPort) -> Signal: + return Signal(self, port) # Signal is added to self._destination_signals in signal constructor + + def add_signal(self, signal: Signal) -> None: + assert signal not in self.signals, \ + "Attempting to connect to Signal already connected." + self._destination_signals.append(signal) + if self is not signal.source: + # Connect this outputport to the signal if it isn't already + signal.set_source(self) + + def disconnect(self, port: InputPort) -> None: + assert port in self.connected_ports, "Attempting to disconnect port that isn't connected." + for sig in self._destination_signals: + if sig.destination is port: + sig.remove_destination() + break + + def remove_signal(self, signal: Signal) -> None: + i: int = self._destination_signals.index(signal) + old_signal: Signal = self._destination_signals[i] + del self._destination_signals[i] + if self is old_signal.source: + old_signal.remove_source() + + def clear(self) -> None: + for signal in self._destination_signals: + self.remove_signal(signal) diff --git a/b_asic/precedence_chart.py b/b_asic/precedence_chart.py new file mode 100644 index 0000000000000000000000000000000000000000..be55a123e0ab4330057c0bb62581e45195f5e5ba --- /dev/null +++ b/b_asic/precedence_chart.py @@ -0,0 +1,21 @@ +"""@package docstring +B-ASIC Precedence Chart Module. +TODO: More info. +""" + +from b_asic.signal_flow_graph import SFG + + +class PrecedenceChart: + """Precedence chart constructed from a signal flow graph. + TODO: More info. + """ + + sfg: SFG + # TODO: More members. + + def __init__(self, sfg: SFG): + self.sfg = sfg + # TODO: Implement. + + # TODO: More stuff. diff --git a/b_asic/schema.py b/b_asic/schema.py new file mode 100644 index 0000000000000000000000000000000000000000..e5068cdc080c5c5004c44c885ac48f52ba44c1f3 --- /dev/null +++ b/b_asic/schema.py @@ -0,0 +1,21 @@ +"""@package docstring +B-ASIC Schema Module. +TODO: More info. +""" + +from b_asic.precedence_chart import PrecedenceChart + + +class Schema: + """Schema constructed from a precedence chart. + TODO: More info. + """ + + pc: PrecedenceChart + # TODO: More members. + + def __init__(self, pc: PrecedenceChart): + self.pc = pc + # TODO: Implement. + + # TODO: More stuff. diff --git a/b_asic/signal.py b/b_asic/signal.py new file mode 100644 index 0000000000000000000000000000000000000000..64c259486abd78e3b18ea824b58bfabe271f50d8 --- /dev/null +++ b/b_asic/signal.py @@ -0,0 +1,96 @@ +"""@package docstring +B-ASIC Signal Module. +""" +from typing import Optional, TYPE_CHECKING + +from b_asic.graph_component import AbstractGraphComponent, TypeName, Name + +if TYPE_CHECKING: + from b_asic.port import InputPort, OutputPort + + +class Signal(AbstractGraphComponent): + """A connection between two ports.""" + + _source: "OutputPort" + _destination: "InputPort" + + def __init__(self, source: Optional["OutputPort"] = None, \ + destination: Optional["InputPort"] = None, name: Name = ""): + + super().__init__(name) + + self._source = source + self._destination = destination + + if source is not None: + self.set_source(source) + + if destination is not None: + self.set_destination(destination) + + @property + def source(self) -> "OutputPort": + """Return the source OutputPort of the signal.""" + return self._source + + @property + def destination(self) -> "InputPort": + """Return the destination "InputPort" of the signal.""" + return self._destination + + def set_source(self, src: "OutputPort") -> None: + """Disconnect the previous source OutputPort of the signal and + connect to the entered source OutputPort. Also connect the entered + source port to the signal if it hasn't already been connected. + + Keyword arguments: + - src: OutputPort to connect as source to the signal. + """ + self.remove_source() + self._source = src + if self not in src.signals: + # If the new source isn't connected to this signal then connect it. + src.add_signal(self) + + def set_destination(self, dest: "InputPort") -> None: + """Disconnect the previous destination InputPort of the signal and + connect to the entered destination InputPort. Also connect the entered + destination port to the signal if it hasn't already been connected. + + Keywords argments: + - dest: InputPort to connect as destination to the signal. + """ + self.remove_destination() + self._destination = dest + if self not in dest.signals: + # If the new destination isn't connected to tis signal then connect it. + dest.add_signal(self) + + @property + def type_name(self) -> TypeName: + return "s" + + def remove_source(self) -> None: + """Disconnect the source OutputPort of the signal. If the source port + still is connected to this signal then also disconnect the source port.""" + if self._source is not None: + old_source: "OutputPort" = self._source + self._source = None + if self in old_source.signals: + # If the old destination port still is connected to this signal, then disconnect it. + old_source.remove_signal(self) + + def remove_destination(self) -> None: + """Disconnect the destination InputPort of the signal.""" + if self._destination is not None: + old_destination: "InputPort" = self._destination + self._destination = None + if self in old_destination.signals: + # If the old destination port still is connected to this signal, then disconnect it. + old_destination.remove_signal(self) + + def is_connected(self) -> bool: + """Returns true if the signal is connected to both a source and a destination, + else false.""" + return self._source is not None and self._destination is not None diff --git a/b_asic/signal_flow_graph.py b/b_asic/signal_flow_graph.py new file mode 100644 index 0000000000000000000000000000000000000000..9c08aecc40ff77b8fe90051b6ea165c0f1703b9b --- /dev/null +++ b/b_asic/signal_flow_graph.py @@ -0,0 +1,91 @@ +"""@package docstring +B-ASIC Signal Flow Graph Module. +TODO: More info. +""" + +from typing import List, Dict, Optional, DefaultDict +from collections import defaultdict + +from b_asic.operation import Operation +from b_asic.operation import AbstractOperation +from b_asic.signal import Signal +from b_asic.graph_id import GraphIDGenerator, GraphID +from b_asic.graph_component import GraphComponent, Name, TypeName + + +class SFG(AbstractOperation): + """Signal flow graph. + TODO: More info. + """ + + _graph_components_by_id: Dict[GraphID, GraphComponent] + _graph_components_by_name: DefaultDict[Name, List[GraphComponent]] + _graph_id_generator: GraphIDGenerator + + def __init__(self, input_signals: List[Signal] = None, output_signals: List[Signal] = None, \ + ops: List[Operation] = None, **kwds): + super().__init__(**kwds) + if input_signals is None: + input_signals = [] + if output_signals is None: + output_signals = [] + if ops is None: + ops = [] + + self._graph_components_by_id = dict() # Maps Graph ID to objects + self._graph_components_by_name = defaultdict(list) # Maps Name to objects + self._graph_id_generator = GraphIDGenerator() + + for operation in ops: + self._add_graph_component(operation) + + for input_signal in input_signals: + self._add_graph_component(input_signal) + + # TODO: Construct SFG based on what inputs that were given + # TODO: Traverse the graph between the inputs/outputs and add to self._operations. + # TODO: Connect ports with signals with appropriate IDs. + + def evaluate(self, *inputs) -> list: + return [] # TODO: Implement + + def _add_graph_component(self, graph_component: GraphComponent) -> GraphID: + """Add the entered graph component to the SFG's dictionary of graph objects and + return a generated GraphID for it. + + Keyword arguments: + graph_component: Graph component to add to the graph. + """ + # Add to name dict + self._graph_components_by_name[graph_component.name].append(graph_component) + + # Add to ID dict + graph_id: GraphID = self._graph_id_generator.get_next_id(graph_component.type_name) + self._graph_components_by_id[graph_id] = graph_component + return graph_id + + def find_by_id(self, graph_id: GraphID) -> Optional[GraphComponent]: + """Find a graph object based on the entered Graph ID and return it. If no graph + object with the entered ID was found then return None. + + Keyword arguments: + graph_id: Graph ID of the wanted object. + """ + if graph_id in self._graph_components_by_id: + return self._graph_components_by_id[graph_id] + + return None + + def find_by_name(self, name: Name) -> List[GraphComponent]: + """Find all graph objects that have the entered name and return them + in a list. If no graph object with the entered name was found then return an + empty list. + + Keyword arguments: + name: Name of the wanted object. + """ + return self._graph_components_by_name[name] + + @property + def type_name(self) -> TypeName: + return "sfg" diff --git a/b_asic/simulation.py b/b_asic/simulation.py new file mode 100644 index 0000000000000000000000000000000000000000..50adaa522b6d685b428354a9f84689330b7fd40f --- /dev/null +++ b/b_asic/simulation.py @@ -0,0 +1,35 @@ +"""@package docstring +B-ASIC Simulation Module. +TODO: More info. +""" + +from numbers import Number +from typing import List + + +class OperationState: + """Simulation state of an operation. + TODO: More info. + """ + + output_values: List[Number] + iteration: int + + def __init__(self): + self.output_values = [] + self.iteration = 0 + + +class SimulationState: + """Simulation state. + TODO: More info. + """ + + # operation_states: Dict[OperationId, OperationState] + iteration: int + + def __init__(self): + self.operation_states = {} + self.iteration = 0 + + # TODO: More stuff. diff --git a/build.sh b/build.sh deleted file mode 100644 index 1015ced245f71f0f85cad0da8ae562acb0d7a550..0000000000000000000000000000000000000000 --- a/build.sh +++ /dev/null @@ -1,5 +0,0 @@ -#! bin/sh -for n in `find . -name "*.py"` -do - python3 $n -done \ No newline at end of file diff --git a/logo.png b/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..b1bd78fe7eb8a737ac8153576078d61492c2e9a3 Binary files /dev/null and b/logo.png differ diff --git a/setup.py b/setup.py new file mode 100644 index 0000000000000000000000000000000000000000..43d55d40a95212196facb973ebc97a1bdc5e7f42 --- /dev/null +++ b/setup.py @@ -0,0 +1,80 @@ +import os +import sys +import shutil +import subprocess +import setuptools +from setuptools import Extension +from setuptools.command.build_ext import build_ext + +CMAKE_EXE = os.environ.get('CMAKE_EXE', shutil.which('cmake')) + +class CMakeExtension(Extension): + def __init__(self, name, sourcedir = ""): + super().__init__(name, sources=[]) + self.sourcedir = os.path.abspath(sourcedir) + +class CMakeBuild(build_ext): + def build_extension(self, ext): + if not isinstance(ext, CMakeExtension): + return super().build_extension(ext) + + if not CMAKE_EXE: + raise RuntimeError(f"Cannot build extension {ext.name}: CMake executable not found! Set the CMAKE_EXE environment variable or update your path.") + + cmake_build_type = "Debug" if self.debug else "Release" + cmake_output_dir = os.path.abspath(os.path.dirname(self.get_ext_fullpath(ext.name))) + cmake_configure_argv = [ + CMAKE_EXE, ext.sourcedir, + "-DCMAKE_BUILD_TYPE=" + cmake_build_type, + "-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=" + cmake_output_dir, + "-DPYTHON_EXECUTABLE=" + sys.executable, + ] + cmake_build_argv = [ + CMAKE_EXE, "--build", ".", + "--config", cmake_build_type + ] + + if not os.path.exists(self.build_temp): + os.makedirs(self.build_temp) + + env = os.environ.copy() + + print(f"=== Configuring {ext.name} ===") + print(f"Temp dir: {self.build_temp}") + print(f"Output dir: {cmake_output_dir}") + subprocess.check_call(cmake_configure_argv, cwd=self.build_temp, env=env) + + print(f"=== Building {ext.name} ===") + print(f"Temp dir: {self.build_temp}") + print(f"Output dir: {cmake_output_dir}") + print(f"Build type: {cmake_build_type}") + subprocess.check_call(cmake_build_argv, cwd=self.build_temp, env=env) + + print() + +setuptools.setup( + name = "b-asic", + version = "0.0.1", + author = "Adam Jakobsson, Angus Lothian, Arvid Westerlund, Felix Goding, Ivar Härnqvist, Jacob Wahlman, Kevin Scott, Rasmus Karlsson", + author_email = "adaja901@student.liu.se, anglo547@student.liu.se, arvwe160@student.liu.se, felgo673@student.liu.se, ivaha717@student.liu.se, jacwa448@student.liu.se, kevsc634@student.liu.se, raska119@student.liu.se", + description = "Better ASIC Toolbox", + long_description = open("README.md", "r").read(), + long_description_content_type = "text/markdown", + url = "https://gitlab.liu.se/PUM_TDDD96/B-ASIC", + license = "MIT", + classifiers = [ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + ], + python_requires = ">=3.6", + install_requires = [ + "pybind11>=2.3.0", + "numpy", + "install_qt_binding" + ], + packages = ["b_asic"], + ext_modules = [CMakeExtension("b_asic")], + cmdclass = {"build_ext": CMakeBuild}, + zip_safe = False +) \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..75a77ef58b86cd29238205a078cec780a6ba9a36 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,21 @@ +#include <pybind11/pybind11.h> + +namespace py = pybind11; + +namespace asic { + +int add(int a, int b) { + return a + b; +} + +int sub(int a, int b) { + return a - b; +} + +} // namespace asic + +PYBIND11_MODULE(_b_asic, m) { + m.doc() = "Better ASIC Toolbox Extension Module."; + m.def("add", &asic::add, "A function which adds two numbers.", py::arg("a"), py::arg("b")); + m.def("sub", &asic::sub, "A function which subtracts two numbers.", py::arg("a"), py::arg("b")); +} \ No newline at end of file diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/test/conftest.py b/test/conftest.py new file mode 100644 index 0000000000000000000000000000000000000000..64f39843c53a4369781a269fd7fc30ad9aa1d255 --- /dev/null +++ b/test/conftest.py @@ -0,0 +1,4 @@ +from test.fixtures.signal import signal, signals +from test.fixtures.operation_tree import * +from test.fixtures.port import * +import pytest diff --git a/test/fixtures/operation_tree.py b/test/fixtures/operation_tree.py new file mode 100644 index 0000000000000000000000000000000000000000..df3fcac35cc495d14bed06ccdfc2a3ebed25616e --- /dev/null +++ b/test/fixtures/operation_tree.py @@ -0,0 +1,58 @@ +from b_asic.core_operations import Addition, Constant +from b_asic.signal import Signal + +import pytest + +@pytest.fixture +def operation(): + return Constant(2) + +def create_operation(_type, dest_oper, index, **kwargs): + oper = _type(**kwargs) + oper_signal = Signal() + oper._output_ports[0].add_signal(oper_signal) + + dest_oper._input_ports[index].add_signal(oper_signal) + return oper + +@pytest.fixture +def operation_tree(): + """Return a addition operation connected with 2 constants. + ---C---+ + ---A + ---C---+ + """ + add_oper = Addition() + create_operation(Constant, add_oper, 0, value=2) + create_operation(Constant, add_oper, 1, value=3) + return add_oper + +@pytest.fixture +def large_operation_tree(): + """Return a constant operation connected with a large operation tree with 3 other constants and 3 additions. + ---C---+ + ---A---+ + ---C---+ | + +---A + ---C---+ | + ---A---+ + ---C---+ + """ + add_oper = Addition() + add_oper_2 = Addition() + + const_oper = create_operation(Constant, add_oper, 0, value=2) + create_operation(Constant, add_oper, 1, value=3) + + create_operation(Constant, add_oper_2, 0, value=4) + create_operation(Constant, add_oper_2, 1, value=5) + + add_oper_3 = Addition() + add_oper_signal = Signal(add_oper.output(0), add_oper_3.output(0)) + add_oper._output_ports[0].add_signal(add_oper_signal) + add_oper_3._input_ports[0].add_signal(add_oper_signal) + + add_oper_2_signal = Signal(add_oper_2.output(0), add_oper_3.output(0)) + add_oper_2._output_ports[0].add_signal(add_oper_2_signal) + add_oper_3._input_ports[1].add_signal(add_oper_2_signal) + return const_oper diff --git a/test/fixtures/port.py b/test/fixtures/port.py new file mode 100644 index 0000000000000000000000000000000000000000..4019b3a2016aa418daeca771f9a2d8bcc4ca6652 --- /dev/null +++ b/test/fixtures/port.py @@ -0,0 +1,10 @@ +import pytest +from b_asic.port import InputPort, OutputPort + +@pytest.fixture +def input_port(): + return InputPort(0, None) + +@pytest.fixture +def output_port(): + return OutputPort(0, None) diff --git a/test/fixtures/signal.py b/test/fixtures/signal.py new file mode 100644 index 0000000000000000000000000000000000000000..7b13c9789f94c33338d08b48373391398bd9f71d --- /dev/null +++ b/test/fixtures/signal.py @@ -0,0 +1,12 @@ +import pytest +from b_asic import Signal + +@pytest.fixture +def signal(): + """Return a signal with no connections.""" + return Signal() + +@pytest.fixture +def signals(): + """Return 3 signals with no connections.""" + return [Signal() for _ in range(0,3)] diff --git a/test/operation/test_abstract_operation.py b/test/operation/test_abstract_operation.py new file mode 100644 index 0000000000000000000000000000000000000000..626a2dc3e5e26fb76d9266dcdd31940681df5c6e --- /dev/null +++ b/test/operation/test_abstract_operation.py @@ -0,0 +1,77 @@ +""" +B-ASIC test suite for the AbstractOperation class. +""" + +from b_asic.core_operations import Addition, ConstantAddition, Subtraction, ConstantSubtraction, \ + Multiplication, ConstantMultiplication, Division, ConstantDivision + +import pytest + + +def test_addition_overload(): + """Tests addition overloading for both operation and number argument.""" + add1 = Addition(None, None, "add1") + add2 = Addition(None, None, "add2") + + add3 = add1 + add2 + + assert isinstance(add3, Addition) + assert add3.input(0).signals == add1.output(0).signals + assert add3.input(1).signals == add2.output(0).signals + + add4 = add3 + 5 + + assert isinstance(add4, ConstantAddition) + assert add4.input(0).signals == add3.output(0).signals + + +def test_subtraction_overload(): + """Tests subtraction overloading for both operation and number argument.""" + add1 = Addition(None, None, "add1") + add2 = Addition(None, None, "add2") + + sub1 = add1 - add2 + + assert isinstance(sub1, Subtraction) + assert sub1.input(0).signals == add1.output(0).signals + assert sub1.input(1).signals == add2.output(0).signals + + sub2 = sub1 - 5 + + assert isinstance(sub2, ConstantSubtraction) + assert sub2.input(0).signals == sub1.output(0).signals + + +def test_multiplication_overload(): + """Tests multiplication overloading for both operation and number argument.""" + add1 = Addition(None, None, "add1") + add2 = Addition(None, None, "add2") + + mul1 = add1 * add2 + + assert isinstance(mul1, Multiplication) + assert mul1.input(0).signals == add1.output(0).signals + assert mul1.input(1).signals == add2.output(0).signals + + mul2 = mul1 * 5 + + assert isinstance(mul2, ConstantMultiplication) + assert mul2.input(0).signals == mul1.output(0).signals + + +def test_division_overload(): + """Tests division overloading for both operation and number argument.""" + add1 = Addition(None, None, "add1") + add2 = Addition(None, None, "add2") + + div1 = add1 / add2 + + assert isinstance(div1, Division) + assert div1.input(0).signals == add1.output(0).signals + assert div1.input(1).signals == add2.output(0).signals + + div2 = div1 / 5 + + assert isinstance(div2, ConstantDivision) + assert div2.input(0).signals == div1.output(0).signals + diff --git a/test/test_core_operations.py b/test/test_core_operations.py new file mode 100644 index 0000000000000000000000000000000000000000..b176b2a6506cc5a1297813f6ddcb6d3589492838 --- /dev/null +++ b/test/test_core_operations.py @@ -0,0 +1,227 @@ +""" +B-ASIC test suite for the core operations. +""" + +from b_asic.core_operations import Constant, Addition, Subtraction, Multiplication, Division, SquareRoot, ComplexConjugate, Max, Min, Absolute, ConstantMultiplication, ConstantAddition, ConstantSubtraction, ConstantDivision + +# Constant tests. +def test_constant(): + constant_operation = Constant(3) + assert constant_operation.evaluate() == 3 + +def test_constant_negative(): + constant_operation = Constant(-3) + assert constant_operation.evaluate() == -3 + +def test_constant_complex(): + constant_operation = Constant(3+4j) + assert constant_operation.evaluate() == 3+4j + +# Addition tests. +def test_addition(): + test_operation = Addition() + constant_operation = Constant(3) + constant_operation_2 = Constant(5) + assert test_operation.evaluate(constant_operation.evaluate(), constant_operation_2.evaluate()) == 8 + +def test_addition_negative(): + test_operation = Addition() + constant_operation = Constant(-3) + constant_operation_2 = Constant(-5) + assert test_operation.evaluate(constant_operation.evaluate(), constant_operation_2.evaluate()) == -8 + +def test_addition_complex(): + test_operation = Addition() + constant_operation = Constant((3+5j)) + constant_operation_2 = Constant((4+6j)) + assert test_operation.evaluate(constant_operation.evaluate(), constant_operation_2.evaluate()) == (7+11j) + +# Subtraction tests. +def test_subtraction(): + test_operation = Subtraction() + constant_operation = Constant(5) + constant_operation_2 = Constant(3) + assert test_operation.evaluate(constant_operation.evaluate(), constant_operation_2.evaluate()) == 2 + +def test_subtraction_negative(): + test_operation = Subtraction() + constant_operation = Constant(-5) + constant_operation_2 = Constant(-3) + assert test_operation.evaluate(constant_operation.evaluate(), constant_operation_2.evaluate()) == -2 + +def test_subtraction_complex(): + test_operation = Subtraction() + constant_operation = Constant((3+5j)) + constant_operation_2 = Constant((4+6j)) + assert test_operation.evaluate(constant_operation.evaluate(), constant_operation_2.evaluate()) == (-1-1j) + +# Multiplication tests. +def test_multiplication(): + test_operation = Multiplication() + constant_operation = Constant(5) + constant_operation_2 = Constant(3) + assert test_operation.evaluate(constant_operation.evaluate(), constant_operation_2.evaluate()) == 15 + +def test_multiplication_negative(): + test_operation = Multiplication() + constant_operation = Constant(-5) + constant_operation_2 = Constant(-3) + assert test_operation.evaluate(constant_operation.evaluate(), constant_operation_2.evaluate()) == 15 + +def test_multiplication_complex(): + test_operation = Multiplication() + constant_operation = Constant((3+5j)) + constant_operation_2 = Constant((4+6j)) + assert test_operation.evaluate(constant_operation.evaluate(), constant_operation_2.evaluate()) == (-18+38j) + +# Division tests. +def test_division(): + test_operation = Division() + constant_operation = Constant(30) + constant_operation_2 = Constant(5) + assert test_operation.evaluate(constant_operation.evaluate(), constant_operation_2.evaluate()) == 6 + +def test_division_negative(): + test_operation = Division() + constant_operation = Constant(-30) + constant_operation_2 = Constant(-5) + assert test_operation.evaluate(constant_operation.evaluate(), constant_operation_2.evaluate()) == 6 + +def test_division_complex(): + test_operation = Division() + constant_operation = Constant((60+40j)) + constant_operation_2 = Constant((10+20j)) + assert test_operation.evaluate(constant_operation.evaluate(), constant_operation_2.evaluate()) == (2.8-1.6j) + +# SquareRoot tests. +def test_squareroot(): + test_operation = SquareRoot() + constant_operation = Constant(36) + assert test_operation.evaluate(constant_operation.evaluate()) == 6 + +def test_squareroot_negative(): + test_operation = SquareRoot() + constant_operation = Constant(-36) + assert test_operation.evaluate(constant_operation.evaluate()) == 6j + +def test_squareroot_complex(): + test_operation = SquareRoot() + constant_operation = Constant((48+64j)) + assert test_operation.evaluate(constant_operation.evaluate()) == (8+4j) + +# ComplexConjugate tests. +def test_complexconjugate(): + test_operation = ComplexConjugate() + constant_operation = Constant(3+4j) + assert test_operation.evaluate(constant_operation.evaluate()) == (3-4j) + +def test_test_complexconjugate_negative(): + test_operation = ComplexConjugate() + constant_operation = Constant(-3-4j) + assert test_operation.evaluate(constant_operation.evaluate()) == (-3+4j) + +# Max tests. +def test_max(): + test_operation = Max() + constant_operation = Constant(30) + constant_operation_2 = Constant(5) + assert test_operation.evaluate(constant_operation.evaluate(), constant_operation_2.evaluate()) == 30 + +def test_max_negative(): + test_operation = Max() + constant_operation = Constant(-30) + constant_operation_2 = Constant(-5) + assert test_operation.evaluate(constant_operation.evaluate(), constant_operation_2.evaluate()) == -5 + +# Min tests. +def test_min(): + test_operation = Min() + constant_operation = Constant(30) + constant_operation_2 = Constant(5) + assert test_operation.evaluate(constant_operation.evaluate(), constant_operation_2.evaluate()) == 5 + +def test_min_negative(): + test_operation = Min() + constant_operation = Constant(-30) + constant_operation_2 = Constant(-5) + assert test_operation.evaluate(constant_operation.evaluate(), constant_operation_2.evaluate()) == -30 + +# Absolute tests. +def test_absolute(): + test_operation = Absolute() + constant_operation = Constant(30) + assert test_operation.evaluate(constant_operation.evaluate()) == 30 + +def test_absolute_negative(): + test_operation = Absolute() + constant_operation = Constant(-5) + assert test_operation.evaluate(constant_operation.evaluate()) == 5 + +def test_absolute_complex(): + test_operation = Absolute() + constant_operation = Constant((3+4j)) + assert test_operation.evaluate(constant_operation.evaluate()) == 5.0 + +# ConstantMultiplication tests. +def test_constantmultiplication(): + test_operation = ConstantMultiplication(5) + constant_operation = Constant(20) + assert test_operation.evaluate(constant_operation.evaluate()) == 100 + +def test_constantmultiplication_negative(): + test_operation = ConstantMultiplication(5) + constant_operation = Constant(-5) + assert test_operation.evaluate(constant_operation.evaluate()) == -25 + +def test_constantmultiplication_complex(): + test_operation = ConstantMultiplication(3+2j) + constant_operation = Constant((3+4j)) + assert test_operation.evaluate(constant_operation.evaluate()) == (1+18j) + +# ConstantAddition tests. +def test_constantaddition(): + test_operation = ConstantAddition(5) + constant_operation = Constant(20) + assert test_operation.evaluate(constant_operation.evaluate()) == 25 + +def test_constantaddition_negative(): + test_operation = ConstantAddition(4) + constant_operation = Constant(-5) + assert test_operation.evaluate(constant_operation.evaluate()) == -1 + +def test_constantaddition_complex(): + test_operation = ConstantAddition(3+2j) + constant_operation = Constant((3+4j)) + assert test_operation.evaluate(constant_operation.evaluate()) == (6+6j) + +# ConstantSubtraction tests. +def test_constantsubtraction(): + test_operation = ConstantSubtraction(5) + constant_operation = Constant(20) + assert test_operation.evaluate(constant_operation.evaluate()) == 15 + +def test_constantsubtraction_negative(): + test_operation = ConstantSubtraction(4) + constant_operation = Constant(-5) + assert test_operation.evaluate(constant_operation.evaluate()) == -9 + +def test_constantsubtraction_complex(): + test_operation = ConstantSubtraction(4+6j) + constant_operation = Constant((3+4j)) + assert test_operation.evaluate(constant_operation.evaluate()) == (-1-2j) + +# ConstantDivision tests. +def test_constantdivision(): + test_operation = ConstantDivision(5) + constant_operation = Constant(20) + assert test_operation.evaluate(constant_operation.evaluate()) == 4 + +def test_constantdivision_negative(): + test_operation = ConstantDivision(4) + constant_operation = Constant(-20) + assert test_operation.evaluate(constant_operation.evaluate()) == -5 + +def test_constantdivision_complex(): + test_operation = ConstantDivision(2+2j) + constant_operation = Constant((10+10j)) + assert test_operation.evaluate(constant_operation.evaluate()) == (5+0j) diff --git a/test/test_graph_id_generator.py b/test/test_graph_id_generator.py new file mode 100644 index 0000000000000000000000000000000000000000..b14597eabe6c15695c5c452f69f3deeab56e36d5 --- /dev/null +++ b/test/test_graph_id_generator.py @@ -0,0 +1,28 @@ +""" +B-ASIC test suite for graph id generator. +""" + +from b_asic.graph_id import GraphIDGenerator, GraphID +import pytest + +@pytest.fixture +def graph_id_generator(): + return GraphIDGenerator() + +class TestGetNextId: + def test_empty_string_generator(self, graph_id_generator): + """Test the graph id generator for an empty string type.""" + assert graph_id_generator.get_next_id("") == "1" + assert graph_id_generator.get_next_id("") == "2" + + def test_normal_string_generator(self, graph_id_generator): + """"Test the graph id generator for a normal string type.""" + assert graph_id_generator.get_next_id("add") == "add1" + assert graph_id_generator.get_next_id("add") == "add2" + + def test_different_strings_generator(self, graph_id_generator): + """Test the graph id generator for different strings.""" + assert graph_id_generator.get_next_id("sub") == "sub1" + assert graph_id_generator.get_next_id("mul") == "mul1" + assert graph_id_generator.get_next_id("sub") == "sub2" + assert graph_id_generator.get_next_id("mul") == "mul2" diff --git a/test/test_inputport.py b/test/test_inputport.py new file mode 100644 index 0000000000000000000000000000000000000000..a43240693ac632b48461023536ff46b0ea379c5c --- /dev/null +++ b/test/test_inputport.py @@ -0,0 +1,95 @@ +""" +B-ASIC test suite for Inputport +""" + +import pytest + +from b_asic import InputPort, OutputPort +from b_asic import Signal + +@pytest.fixture +def inp_port(): + return InputPort(0, None) + +@pytest.fixture +def out_port(): + return OutputPort(0, None) + +@pytest.fixture +def out_port2(): + return OutputPort(1, None) + +@pytest.fixture +def dangling_sig(): + return Signal() + +@pytest.fixture +def s_w_source(): + out_port = OutputPort(0, None) + return Signal(source=out_port) + +@pytest.fixture +def sig_with_dest(): + inp_port = InputPort(0, None) + return Signal(destination=out_port) + +@pytest.fixture +def connected_sig(): + out_port = OutputPort(0, None) + inp_port = InputPort(0, None) + return Signal(source=out_port, destination=inp_port) + +def test_connect_then_disconnect(inp_port, out_port): + """Test connect unused port to port.""" + s1 = inp_port.connect(out_port) + + assert inp_port.connected_ports == [out_port] + assert out_port.connected_ports == [inp_port] + assert inp_port.signals == [s1] + assert out_port.signals == [s1] + assert s1.source is out_port + assert s1.destination is inp_port + + inp_port.remove_signal(s1) + + assert inp_port.connected_ports == [] + assert out_port.connected_ports == [] + assert inp_port.signals == [] + assert out_port.signals == [s1] + assert s1.source is out_port + assert s1.destination is None + +def test_connect_used_port_to_new_port(inp_port, out_port, out_port2): + """Does connecting multiple ports to an inputport throw error?""" + inp_port.connect(out_port) + with pytest.raises(AssertionError): + inp_port.connect(out_port2) + +def test_add_signal_then_disconnect(inp_port, s_w_source): + """Can signal be connected then disconnected properly?""" + inp_port.add_signal(s_w_source) + + assert inp_port.connected_ports == [s_w_source.source] + assert s_w_source.source.connected_ports == [inp_port] + assert inp_port.signals == [s_w_source] + assert s_w_source.source.signals == [s_w_source] + assert s_w_source.destination is inp_port + + inp_port.remove_signal(s_w_source) + + assert inp_port.connected_ports == [] + assert s_w_source.source.connected_ports == [] + assert inp_port.signals == [] + assert s_w_source.source.signals == [s_w_source] + assert s_w_source.destination is None + +def test_connect_then_disconnect(inp_port, out_port): + """Can port be connected and then disconnected properly?""" + inp_port.connect(out_port) + + inp_port.disconnect(out_port) + + print("outport signals:", out_port.signals, "count:", out_port.signal_count()) + assert inp_port.signal_count() == 1 + assert len(inp_port.connected_ports) == 0 + assert out_port.signal_count() == 0 diff --git a/test/test_operation.py b/test/test_operation.py new file mode 100644 index 0000000000000000000000000000000000000000..6c37e30bddd0b55ea69ae5b95a341c1ddeb56847 --- /dev/null +++ b/test/test_operation.py @@ -0,0 +1,31 @@ +from b_asic.core_operations import Constant, Addition +from b_asic.signal import Signal +from b_asic.port import InputPort, OutputPort + +import pytest + +class TestTraverse: + def test_traverse_single_tree(self, operation): + """Traverse a tree consisting of one operation.""" + constant = Constant(None) + assert list(constant.traverse()) == [constant] + + def test_traverse_tree(self, operation_tree): + """Traverse a basic addition tree with two constants.""" + assert len(list(operation_tree.traverse())) == 3 + + def test_traverse_large_tree(self, large_operation_tree): + """Traverse a larger tree.""" + assert len(list(large_operation_tree.traverse())) == 7 + + def test_traverse_type(self, large_operation_tree): + traverse = list(large_operation_tree.traverse()) + assert len(list(filter(lambda type_: isinstance(type_, Addition), traverse))) == 3 + assert len(list(filter(lambda type_: isinstance(type_, Constant), traverse))) == 4 + + def test_traverse_loop(self, operation_tree): + add_oper_signal = Signal() + operation_tree._output_ports[0].add_signal(add_oper_signal) + operation_tree._input_ports[0].remove_signal(add_oper_signal) + operation_tree._input_ports[0].add_signal(add_oper_signal) + assert len(list(operation_tree.traverse())) == 2 diff --git a/test/test_outputport.py b/test/test_outputport.py new file mode 100644 index 0000000000000000000000000000000000000000..deed7a1e06836600254e3903b8b45a3d05f17cbe --- /dev/null +++ b/test/test_outputport.py @@ -0,0 +1,80 @@ +""" +B-ASIC test suite for OutputPort. +""" +from b_asic import OutputPort, InputPort, Signal +import pytest + +@pytest.fixture +def output_port(): + return OutputPort(0, None) + +@pytest.fixture +def input_port(): + return InputPort(0, None) + +@pytest.fixture +def list_of_input_ports(): + return [InputPort(_, None) for _ in range(0,3)] + +class TestConnect: + def test_multiple_ports(self, output_port, list_of_input_ports): + """Can multiple ports connect to an output port?""" + for port in list_of_input_ports: + output_port.connect(port) + + assert output_port.signal_count() == len(list_of_input_ports) + + def test_same_port(self, output_port, list_of_input_ports): + """Check error handing.""" + output_port.connect(list_of_input_ports[0]) + with pytest.raises(AssertionError): + output_port.connect(list_of_input_ports[0]) + + assert output_port.signal_count() == 2 + +class TestAddSignal: + def test_dangling(self, output_port): + s = Signal() + output_port.add_signal(s) + + assert output_port.signal_count() == 1 + + def test_with_destination(self, output_port, input_port): + s = Signal(destination=input_port) + output_port.add_signal(s) + + assert output_port.connected_ports == [s.destination] + +class TestDisconnect: + def test_multiple_ports(self, output_port, list_of_input_ports): + """Can multiple ports disconnect from OutputPort?""" + for port in list_of_input_ports: + output_port.connect(port) + + for port in list_of_input_ports: + output_port.disconnect(port) + + assert output_port.signal_count() == 3 + assert output_port.connected_ports == [] + +class TestRemoveSignal: + def test_one_signal(self, output_port, input_port): + s = output_port.connect(input_port) + output_port.remove_signal(s) + + assert output_port.signal_count() == 0 + assert output_port.signals == [] + assert output_port.connected_ports == [] + + def test_multiple_signals(self, output_port, list_of_input_ports): + """Can multiple signals disconnect from OutputPort?""" + sigs = [] + + for port in list_of_input_ports: + sigs.append(output_port.connect(port)) + + for sig in sigs: + output_port.remove_signal(sig) + + assert output_port.signal_count() == 0 + assert output_port.signals == [] diff --git a/test/test_signal.py b/test/test_signal.py new file mode 100644 index 0000000000000000000000000000000000000000..ab07eb778ddb693bfc9cfabf6aeb7804038312d5 --- /dev/null +++ b/test/test_signal.py @@ -0,0 +1,62 @@ +""" +B-ASIC test suit for the signal module which consists of the Signal class. +""" + +from b_asic.port import InputPort, OutputPort +from b_asic.signal import Signal + +import pytest + +def test_signal_creation_and_disconnction_and_connection_changing(): + in_port = InputPort(0, None) + out_port = OutputPort(1, None) + s = Signal(out_port, in_port) + + assert in_port.signals == [s] + assert out_port.signals == [s] + assert s.source is out_port + assert s.destination is in_port + + in_port1 = InputPort(0, None) + s.set_destination(in_port1) + + assert in_port.signals == [] + assert in_port1.signals == [s] + assert out_port.signals == [s] + assert s.source is out_port + assert s.destination is in_port1 + + s.remove_source() + + assert out_port.signals == [] + assert in_port1.signals == [s] + assert s.source is None + assert s.destination is in_port1 + + s.remove_destination() + + assert out_port.signals == [] + assert in_port1.signals == [] + assert s.source is None + assert s.destination is None + + out_port1 = OutputPort(0, None) + s.set_source(out_port1) + + assert out_port1.signals == [s] + assert s.source is out_port1 + assert s.destination is None + + s.set_source(out_port) + + assert out_port.signals == [s] + assert out_port1.signals == [] + assert s.source is out_port + assert s.destination is None + + s.set_destination(in_port) + + assert out_port.signals == [s] + assert in_port.signals == [s] + assert s.source is out_port + assert s.destination is in_port