Skip to content
Snippets Groups Projects
Commit b706fa0a authored by Ivar Härnqvist's avatar Ivar Härnqvist
Browse files

Add support for FetchContent and clang-tidy, modernize C++ code and fix...

Add support for FetchContent and clang-tidy, modernize C++ code and fix clang-tidy warnings, fix undefined behavior in simulation due to order of evaluation
parent 1b75d8c3
No related branches found
No related tags found
1 merge request!76FetchContent support
Pipeline #59955 passed
Showing
with 796 additions and 105 deletions
Checks: "clang-analyzer-*,
cppcoreguidelines-*,
misc-*,
modernize-*,
performance-*,
portability-*,
readability-*,
-cppcoreguidelines-avoid-c-arrays,
-cppcoreguidelines-avoid-magic-numbers,
-cppcoreguidelines-macro-usage,
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
-cppcoreguidelines-pro-bounds-pointer-arithmetic,
-cppcoreguidelines-pro-type-union-access,
-misc-non-private-member-variables-in-classes,
-misc-no-recursion,
-modernize-avoid-c-arrays,
-modernize-use-trailing-return-type,
-readability-function-cognitive-complexity,
-readability-magic-numbers,
-readability-named-parameter"
WarningsAsErrors: "*"
HeaderFilterRegex: "src/"
cmake_minimum_required(VERSION 3.8) cmake_minimum_required(VERSION 3.8)
project("B-ASIC"
project(
"B-ASIC"
VERSION 1.0.0 VERSION 1.0.0
DESCRIPTION "Better ASIC Toolbox for Python 3" DESCRIPTION "Better ASIC Toolbox for Python 3"
LANGUAGES C CXX LANGUAGES C CXX)
)
# Find dependencies. option(ASIC_USE_FETCHCONTENT "Automatically download dependencies" ON)
find_package(fmt REQUIRED) option(ASIC_USE_CLANG_TIDY "Use clang-tidy for static analysis" OFF)
find_package(pybind11 CONFIG REQUIRED) option(ASIC_BUILDING_PYTHON_DISTRIBUTION "Don't copy compiled binaries to project directory" OFF)
set(LIBRARY_NAME "b_asic") # Name of the python library directory. set(LIBRARY_NAME "b_asic") # Name of the python library directory.
set(TARGET_NAME "_${LIBRARY_NAME}") # Name of this extension module. set(TARGET_NAME "_${LIBRARY_NAME}") # Name of this extension module.
# Find dependencies.
if(ASIC_USE_FETCHCONTENT)
add_subdirectory(dependencies)
else()
find_package(fmt REQUIRED)
find_package(pybind11 CONFIG REQUIRED)
endif()
# Set output directory for compiled binaries. # Set output directory for compiled binaries.
if(NOT CMAKE_LIBRARY_OUTPUT_DIRECTORY) if(NOT CMAKE_LIBRARY_OUTPUT_DIRECTORY)
include(GNUInstallDirs) include(GNUInstallDirs)
...@@ -32,9 +37,7 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}") ...@@ -32,9 +37,7 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}") set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}")
# Add files to be compiled into Python module. # Add files to be compiled into Python module.
pybind11_add_module( pybind11_add_module("${TARGET_NAME}"
"${TARGET_NAME}"
# Main files. # Main files.
"${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/simulation.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/simulation.cpp"
...@@ -53,49 +56,44 @@ pybind11_add_module( ...@@ -53,49 +56,44 @@ pybind11_add_module(
) )
# Include headers. # Include headers.
target_include_directories( target_include_directories("${TARGET_NAME}" PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src")
"${TARGET_NAME}"
PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}/src"
)
# Use C++17. # Use C++17.
target_compile_features( target_compile_features("${TARGET_NAME}" PRIVATE cxx_std_17)
"${TARGET_NAME}"
PRIVATE
cxx_std_17
)
# Set compiler-specific options using generator expressions. # Set compiler-specific options using generator expressions.
target_compile_options( target_compile_options("${TARGET_NAME}" PRIVATE
"${TARGET_NAME}" $<$<CXX_COMPILER_ID:GNU>: -std=c++17 -Wall -Wextra -Wpedantic -Werror -Wno-psabi $<$<CONFIG:Debug>:-Og -g> $<$<CONFIG:Release>:-O3> $<$<CONFIG:MinSizeRel>:-Os> $<$<CONFIG:RelWithDebInfo>:-O3 -g>>
PRIVATE $<$<CXX_COMPILER_ID:Clang>: -std=c++17 -Wall -Wextra -Wpedantic -Werror $<$<CONFIG:Debug>:-Og -g> $<$<CONFIG:Release>:-O3> $<$<CONFIG:MinSizeRel>:-Os> $<$<CONFIG:RelWithDebInfo>:-O3 -g>>
$<$<OR:$<CXX_COMPILER_ID:GNU>,$<CXX_COMPILER_ID:Clang>>: $<$<CXX_COMPILER_ID:MSVC>: /std:c++17 /W3 /permissive- /WX /wd4996 /utf-8 $<$<CONFIG:Debug>:/Od> $<$<CONFIG:Release>:/Ot> $<$<CONFIG:MinSizeRel>:/Os> $<$<CONFIG:RelWithDebInfo>:/Ot /Od>>)
-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>
>
)
# Add libraries. Note: pybind11 is already added in pybind11_add_module. # Add libraries. Note: pybind11 is already added in pybind11_add_module.
target_link_libraries( if(ASIC_USE_FETCHCONTENT)
"${TARGET_NAME}" target_link_libraries("${TARGET_NAME}" PRIVATE
PRIVATE dependency_fmt)
else()
target_link_libraries("${TARGET_NAME}" PRIVATE
$<TARGET_NAME_IF_EXISTS:fmt::fmt-header-only> $<TARGET_NAME_IF_EXISTS:fmt::fmt-header-only>
$<$<NOT:$<TARGET_EXISTS:fmt::fmt-header-only>>:fmt::fmt> $<$<NOT:$<TARGET_EXISTS:fmt::fmt-header-only>>:fmt::fmt>)
) endif()
# Set up clang-tidy.
if(ASIC_USE_CLANG_TIDY)
find_program(CLANG_TIDY NAMES clang-tidy REQUIRED)
set_property(TARGET "${TARGET_NAME}" PROPERTY CXX_CLANG_TIDY ${CLANG_TIDY})
if(MSVC)
set_target_properties("${TARGET_NAME}" PROPERTIES
VS_GLOBAL_RunCodeAnalysis true
VS_GLOBAL_EnableMicrosoftCodeAnalysis false
VS_GLOBAL_EnableClangTidyCodeAnalysis true
VS_GLOBAL_ClangTidyToolExe "${CLANG_TIDY}")
endif()
endif()
# Copy binaries to project folder for debugging during development. # Copy binaries to project folder for debugging during development.
if(NOT ASIC_BUILDING_PYTHON_DISTRIBUTION) if(NOT ASIC_BUILDING_PYTHON_DISTRIBUTION)
add_custom_target( add_custom_target(copy_binaries ALL
copy_binaries ALL
COMMAND ${CMAKE_COMMAND} -E copy "$<TARGET_FILE:${TARGET_NAME}>" "${CMAKE_CURRENT_LIST_DIR}" COMMAND ${CMAKE_COMMAND} -E copy "$<TARGET_FILE:${TARGET_NAME}>" "${CMAKE_CURRENT_LIST_DIR}"
COMMENT "Copying binaries to ${CMAKE_CURRENT_LIST_DIR}" COMMENT "Copying binaries to ${CMAKE_CURRENT_LIST_DIR}"
DEPENDS "${TARGET_NAME}" DEPENDS "${TARGET_NAME}")
)
endif() endif()
...@@ -2,4 +2,4 @@ include README.md ...@@ -2,4 +2,4 @@ include README.md
include LICENSE include LICENSE
include CMakeLists.txt include CMakeLists.txt
include b_asic/GUI/operation_icons/* include b_asic/GUI/operation_icons/*
recursive-include src *.cpp *.h recursive-include src *.cpp *.hpp
<img src="logo.png" width="278" height="100"> <img src="logo.png" width="278" height="100">
# B-ASIC - Better ASIC Toolbox # B-ASIC - Better ASIC Toolbox
B-ASIC is an ASIC toolbox for Python 3 that simplifies circuit design and optimization. B-ASIC is an ASIC toolbox for Python 3 that simplifies circuit design and optimization.
## Development ## Development
How to build and debug the library during development. How to build and debug the library during development.
### Prerequisites ### Prerequisites
The following packages are required in order to build the library: The following packages are required in order to build the library:
* cmake 3.8+
* gcc 7+/clang 7+/msvc 16+ - cmake 3.8+
* fmtlib - gcc 7+/clang 7+/msvc 16+
* pybind11 2.3.0+ - fmtlib
* python 3.6+ - pybind11 2.3.0+
* Python: - python 3.6+
* graphviz - Python:
* matplotlib - graphviz
* numpy - matplotlib
* pybind11 - numpy
* pyside2 - pybind11
* qtpy - pyside2
* scipy - qtpy
* setuptools - scipy
- setuptools
To build a binary distribution, the following additional packages are required: To build a binary distribution, the following additional packages are required:
* Python:
* wheel - Python:
- wheel
To run the test suite, the following additional packages are required: To run the test suite, the following additional packages are required:
* Python:
* pytest - Python:
* pytest-cov (for testing with coverage) - pytest
- pytest-cov (for testing with coverage)
To generate the documentation, the following additional packages are required: To generate the documentation, the following additional packages are required:
* doxygen
- doxygen
### Using CMake directly ### Using CMake directly
How to build using CMake. How to build using CMake.
#### Configuring #### Configuring
In `B-ASIC`: In `B-ASIC`:
``` ```
mkdir build mkdir build
cd build cd build
...@@ -47,53 +57,73 @@ cmake .. ...@@ -47,53 +57,73 @@ cmake ..
``` ```
#### Building (Debug) #### Building (Debug)
In `B-ASIC/build`: In `B-ASIC/build`:
``` ```
cmake --build . cmake --build .
``` ```
The output gets written to `B-ASIC/build/lib`. The output gets written to `B-ASIC/build/lib`.
#### Building (Release) #### Building (Release)
In `B-ASIC/build`: In `B-ASIC/build`:
``` ```
cmake --build . --config Release cmake --build . --config Release
``` ```
The output gets written to `B-ASIC/build/lib`. The output gets written to `B-ASIC/build/lib`.
### Using setuptools to create a package ### Using setuptools to create a package
How to create a package using setuptools that can be installed using pip. How to create a package using setuptools that can be installed using pip.
#### Setup (Binary distribution) #### Setup (Binary distribution)
In `B-ASIC`: In `B-ASIC`:
``` ```
python3 setup.py bdist_wheel python3 setup.py bdist_wheel
``` ```
The output gets written to `B-ASIC/dist/b_asic-<version>-<python_tag>-<abi_tag>-<platform_tag>.whl`. The output gets written to `B-ASIC/dist/b_asic-<version>-<python_tag>-<abi_tag>-<platform_tag>.whl`.
#### Setup (Source distribution) #### Setup (Source distribution)
In `B-ASIC`: In `B-ASIC`:
``` ```
python3 setup.py sdist python3 setup.py sdist
``` ```
The output gets written to `B-ASIC/dist/b-asic-<version>.tar.gz`. The output gets written to `B-ASIC/dist/b-asic-<version>.tar.gz`.
#### Installation (Binary distribution) #### Installation (Binary distribution)
In `B-ASIC/dist`: In `B-ASIC/dist`:
``` ```
pip install b_asic-<version>-<python_tag>-<abi_tag>-<platform_tag>.whl pip install b_asic-<version>-<python_tag>-<abi_tag>-<platform_tag>.whl
``` ```
#### Installation (Source distribution) #### Installation (Source distribution)
In `B-ASIC/dist`: In `B-ASIC/dist`:
``` ```
pip install b-asic-<version>.tar.gz pip install b-asic-<version>.tar.gz
``` ```
### Running tests ### Running tests
How to run the tests using pytest in a virtual environment. How to run the tests using pytest in a virtual environment.
#### Linux/OS X #### Linux/OS X
In `B-ASIC`: In `B-ASIC`:
``` ```
python3 -m venv env python3 -m venv env
source env/bin/activate source env/bin/activate
...@@ -102,7 +132,9 @@ pytest ...@@ -102,7 +132,9 @@ pytest
``` ```
#### Windows #### Windows
In `B-ASIC` (as admin): In `B-ASIC` (as admin):
``` ```
python3 -m venv env python3 -m venv env
.\env\Scripts\activate.bat .\env\Scripts\activate.bat
...@@ -111,26 +143,33 @@ pytest ...@@ -111,26 +143,33 @@ pytest
``` ```
#### Test with coverage #### Test with coverage
``` ```
pytest --cov=b_asic --cov-report html test pytest --cov=b_asic --cov-report html test
``` ```
### Generating documentation ### Generating documentation
In `B-ASIC`: In `B-ASIC`:
``` ```
doxygen doxygen
``` ```
The output gets written to `B-ASIC/doc`. The output gets written to `B-ASIC/doc`.
## Usage ## Usage
How to build and use the library as a user. How to build and use the library as a user.
### Installation ### Installation
``` ```
pip install b_asic pip install b_asic
``` ```
### Importing ### Importing
``` ```
python3 python3
>>> import b_asic as asic >>> import b_asic as asic
...@@ -138,5 +177,6 @@ python3 ...@@ -138,5 +177,6 @@ python3
``` ```
## License ## License
B-ASIC is distributed under the MIT license. B-ASIC is distributed under the MIT license.
See the included LICENSE file for more information. See the included LICENSE file for more information.
include(FetchContent)
add_subdirectory(fmt)
add_subdirectory(pybind11)
message(STATUS "Fetching fmt...")
FetchContent_Declare(fmt
GIT_REPOSITORY https://github.com/fmtlib/fmt
GIT_TAG d141cdbeb0fb422a3fb7173b285fd38e0d1772dc # 8.0.1
)
FetchContent_MakeAvailable(fmt)
add_library(dependency_fmt INTERFACE)
target_include_directories(dependency_fmt SYSTEM INTERFACE $<TARGET_PROPERTY:fmt-header-only,INTERFACE_INCLUDE_DIRECTORIES>)
target_link_libraries(dependency_fmt INTERFACE fmt-header-only)
message(STATUS "Fetching pybind11...")
FetchContent_Declare(pybind11
GIT_REPOSITORY https://github.com/pybind/pybind11
GIT_TAG f7b499615e14d70ab098a20deb0cdb3889998a1a # 2.8.1
)
FetchContent_MakeAvailable(pybind11)
...@@ -8,4 +8,4 @@ or to be used as a refererence for future development. ...@@ -8,4 +8,4 @@ or to be used as a refererence for future development.
This folder contains a C++ implementation of the Simulation class designed This folder contains a C++ implementation of the Simulation class designed
using Object-Oriented Programming, as opposed to the current version that uses using Object-Oriented Programming, as opposed to the current version that uses
Data-Oriented Design. They are functionally identical, but use different Data-Oriented Design. They are functionally identical, but use different
styles of programming and have different performance characteristics. styles of programming and have different performance characteristics.
\ No newline at end of file
#ifndef ASIC_SIMULATION_CORE_OPERATIONS_HPP
#define ASIC_SIMULATION_CORE_OPERATIONS_HPP
#include "../debug.hpp"
#include "../number.hpp"
#include "operation.hpp"
#include <algorithm>
#include <cmath>
#include <cstddef>
#include <stdexcept>
#include <utility>
namespace asic {
class constant_operation final : public abstract_operation {
public:
constant_operation(result_key key, number value)
: abstract_operation(std::move(key))
, m_value(value) {}
[[nodiscard]] std::size_t output_count() const noexcept final {
return 1;
}
private:
[[nodiscard]] number evaluate_output_impl(std::size_t, evaluation_context const&) const final {
ASIC_DEBUG_MSG("Evaluating constant.");
return m_value;
}
number m_value;
};
class addition_operation final : public binary_operation {
public:
explicit addition_operation(result_key key)
: binary_operation(std::move(key)) {}
[[nodiscard]] std::size_t output_count() const noexcept final {
return 1;
}
private:
[[nodiscard]] number evaluate_output_impl(std::size_t, evaluation_context const& context) const final {
ASIC_DEBUG_MSG("Evaluating addition.");
return this->evaluate_lhs(context) + this->evaluate_rhs(context);
}
};
class subtraction_operation final : public binary_operation {
public:
explicit subtraction_operation(result_key key)
: binary_operation(std::move(key)) {}
[[nodiscard]] std::size_t output_count() const noexcept final {
return 1;
}
private:
[[nodiscard]] number evaluate_output_impl(std::size_t, evaluation_context const& context) const final {
ASIC_DEBUG_MSG("Evaluating subtraction.");
return this->evaluate_lhs(context) - this->evaluate_rhs(context);
}
};
class multiplication_operation final : public binary_operation {
public:
explicit multiplication_operation(result_key key)
: binary_operation(std::move(key)) {}
[[nodiscard]] std::size_t output_count() const noexcept final {
return 1;
}
private:
[[nodiscard]] number evaluate_output_impl(std::size_t, evaluation_context const& context) const final {
ASIC_DEBUG_MSG("Evaluating multiplication.");
return this->evaluate_lhs(context) * this->evaluate_rhs(context);
}
};
class division_operation final : public binary_operation {
public:
explicit division_operation(result_key key)
: binary_operation(std::move(key)) {}
[[nodiscard]] std::size_t output_count() const noexcept final {
return 1;
}
private:
[[nodiscard]] number evaluate_output_impl(std::size_t, evaluation_context const& context) const final {
ASIC_DEBUG_MSG("Evaluating division.");
return this->evaluate_lhs(context) / this->evaluate_rhs(context);
}
};
class min_operation final : public binary_operation {
public:
explicit min_operation(result_key key)
: binary_operation(std::move(key)) {}
[[nodiscard]] std::size_t output_count() const noexcept final {
return 1;
}
private:
[[nodiscard]] number evaluate_output_impl(std::size_t, evaluation_context const& context) const final {
ASIC_DEBUG_MSG("Evaluating min.");
auto const lhs = this->evaluate_lhs(context);
if (lhs.imag() != 0) {
throw std::runtime_error{"Min does not support complex numbers."};
}
auto const rhs = this->evaluate_rhs(context);
if (rhs.imag() != 0) {
throw std::runtime_error{"Min does not support complex numbers."};
}
return std::min(lhs.real(), rhs.real());
}
};
class max_operation final : public binary_operation {
public:
explicit max_operation(result_key key)
: binary_operation(std::move(key)) {}
[[nodiscard]] std::size_t output_count() const noexcept final {
return 1;
}
private:
[[nodiscard]] number evaluate_output_impl(std::size_t, evaluation_context const& context) const final {
ASIC_DEBUG_MSG("Evaluating max.");
auto const lhs = this->evaluate_lhs(context);
if (lhs.imag() != 0) {
throw std::runtime_error{"Max does not support complex numbers."};
}
auto const rhs = this->evaluate_rhs(context);
if (rhs.imag() != 0) {
throw std::runtime_error{"Max does not support complex numbers."};
}
return std::max(lhs.real(), rhs.real());
}
};
class square_root_operation final : public unary_operation {
public:
explicit square_root_operation(result_key key)
: unary_operation(std::move(key)) {}
[[nodiscard]] std::size_t output_count() const noexcept final {
return 1;
}
private:
[[nodiscard]] number evaluate_output_impl(std::size_t, evaluation_context const& context) const final {
ASIC_DEBUG_MSG("Evaluating sqrt.");
return std::sqrt(this->evaluate_input(context));
}
};
class complex_conjugate_operation final : public unary_operation {
public:
explicit complex_conjugate_operation(result_key key)
: unary_operation(std::move(key)) {}
[[nodiscard]] std::size_t output_count() const noexcept final {
return 1;
}
private:
[[nodiscard]] number evaluate_output_impl(std::size_t, evaluation_context const& context) const final {
ASIC_DEBUG_MSG("Evaluating conj.");
return std::conj(this->evaluate_input(context));
}
};
class absolute_operation final : public unary_operation {
public:
explicit absolute_operation(result_key key)
: unary_operation(std::move(key)) {}
[[nodiscard]] std::size_t output_count() const noexcept final {
return 1;
}
private:
[[nodiscard]] number evaluate_output_impl(std::size_t, evaluation_context const& context) const final {
ASIC_DEBUG_MSG("Evaluating abs.");
return std::abs(this->evaluate_input(context));
}
};
class constant_multiplication_operation final : public unary_operation {
public:
constant_multiplication_operation(result_key key, number value)
: unary_operation(std::move(key))
, m_value(value) {}
[[nodiscard]] std::size_t output_count() const noexcept final {
return 1;
}
private:
[[nodiscard]] number evaluate_output_impl(std::size_t, evaluation_context const& context) const final {
ASIC_DEBUG_MSG("Evaluating cmul.");
return this->evaluate_input(context) * m_value;
}
number m_value;
};
class butterfly_operation final : public binary_operation {
public:
explicit butterfly_operation(result_key key)
: binary_operation(std::move(key)) {}
[[nodiscard]] std::size_t output_count() const noexcept final {
return 2;
}
private:
[[nodiscard]] number evaluate_output_impl(std::size_t index, evaluation_context const& context) const final {
ASIC_DEBUG_MSG("Evaluating bfly.");
if (index == 0) {
return this->evaluate_lhs(context) + this->evaluate_rhs(context);
}
return this->evaluate_lhs(context) - this->evaluate_rhs(context);
}
};
} // namespace asic
#endif // ASIC_SIMULATION_CORE_OPERATIONS_HPP
#include "custom_operation.h" #include "custom_operation.hpp"
#define NOMINMAX
#include <pybind11/stl.h> #include <pybind11/stl.h>
namespace py = pybind11;
namespace asic { namespace asic {
custom_operation::custom_operation(result_key key, pybind11::object evaluate_output, pybind11::object truncate_input, custom_operation::custom_operation(result_key key, pybind11::object evaluate_output, pybind11::object truncate_input,
...@@ -27,4 +26,4 @@ number custom_operation::truncate_input(std::size_t index, number value, std::si ...@@ -27,4 +26,4 @@ number custom_operation::truncate_input(std::size_t index, number value, std::si
return m_truncate_input(index, value, bits).cast<number>(); return m_truncate_input(index, value, bits).cast<number>();
} }
} // namespace asic } // namespace asic
\ No newline at end of file
#ifndef ASIC_SIMULATION_CUSTOM_OPERATION_HPP
#define ASIC_SIMULATION_CUSTOM_OPERATION_HPP
#include "../algorithm.hpp"
#include "../debug.hpp"
#include "../number.hpp"
#include "operation.hpp"
#define NOMINMAX
#include <cstddef>
#include <fmt/format.h>
#include <functional>
#include <pybind11/pybind11.h>
#include <stdexcept>
#include <utility>
namespace asic {
class custom_operation final : public nary_operation {
public:
custom_operation(result_key key, pybind11::object evaluate_output, pybind11::object truncate_input, std::size_t output_count);
[[nodiscard]] std::size_t output_count() const noexcept final;
private:
[[nodiscard]] number evaluate_output_impl(std::size_t index, evaluation_context const& context) const final;
[[nodiscard]] number truncate_input(std::size_t index, number value, std::size_t bits) const final;
pybind11::object m_evaluate_output;
pybind11::object m_truncate_input;
std::size_t m_output_count;
};
} // namespace asic
#endif // ASIC_SIMULATION_CUSTOM_OPERATION_HPP
#include "operation.h" #include "operation.hpp"
#include "../debug.h" #include "../debug.hpp"
#define NOMINMAX
#include <pybind11/pybind11.h> #include <pybind11/pybind11.h>
namespace py = pybind11; namespace py = pybind11;
...@@ -11,7 +12,7 @@ namespace asic { ...@@ -11,7 +12,7 @@ namespace asic {
signal_source::signal_source(std::shared_ptr<const operation> op, std::size_t index, std::optional<std::size_t> bits) signal_source::signal_source(std::shared_ptr<const operation> op, std::size_t index, std::optional<std::size_t> bits)
: m_operation(std::move(op)) : m_operation(std::move(op))
, m_index(index) , m_index(index)
, m_bits(std::move(bits)) {} , m_bits(bits) {}
signal_source::operator bool() const noexcept { signal_source::operator bool() const noexcept {
return static_cast<bool>(m_operation); return static_cast<bool>(m_operation);
...@@ -100,7 +101,7 @@ signal_source const& unary_operation::input() const noexcept { ...@@ -100,7 +101,7 @@ signal_source const& unary_operation::input() const noexcept {
number unary_operation::evaluate_input(evaluation_context const& context) const { number unary_operation::evaluate_input(evaluation_context const& context) const {
auto const value = m_in.evaluate_output(context); auto const value = m_in.evaluate_output(context);
auto const bits = context.bits_override.value_or(m_in.bits().value_or(0)); auto const bits = context.bits_override.value_or(m_in.bits().value_or(0));
return (context.truncate && bits) ? this->truncate_input(0, value, bits) : value; return (context.truncate && bits != 0) ? this->truncate_input(0, value, bits) : value;
} }
binary_operation::binary_operation(result_key key) binary_operation::binary_operation(result_key key)
...@@ -122,13 +123,13 @@ signal_source const& binary_operation::rhs() const noexcept { ...@@ -122,13 +123,13 @@ signal_source const& binary_operation::rhs() const noexcept {
number binary_operation::evaluate_lhs(evaluation_context const& context) const { number binary_operation::evaluate_lhs(evaluation_context const& context) const {
auto const value = m_lhs.evaluate_output(context); auto const value = m_lhs.evaluate_output(context);
auto const bits = context.bits_override.value_or(m_lhs.bits().value_or(0)); auto const bits = context.bits_override.value_or(m_lhs.bits().value_or(0));
return (context.truncate && bits) ? this->truncate_input(0, value, bits) : value; return (context.truncate && bits != 0) ? this->truncate_input(0, value, bits) : value;
} }
number binary_operation::evaluate_rhs(evaluation_context const& context) const { number binary_operation::evaluate_rhs(evaluation_context const& context) const {
auto const value = m_rhs.evaluate_output(context); auto const value = m_rhs.evaluate_output(context);
auto const bits = context.bits_override.value_or(m_rhs.bits().value_or(0)); auto const bits = context.bits_override.value_or(m_rhs.bits().value_or(0));
return (context.truncate && bits) ? this->truncate_input(0, value, bits) : value; return (context.truncate && bits != 0) ? this->truncate_input(0, value, bits) : value;
} }
nary_operation::nary_operation(result_key key) nary_operation::nary_operation(result_key key)
...@@ -148,9 +149,9 @@ std::vector<number> nary_operation::evaluate_inputs(evaluation_context const& co ...@@ -148,9 +149,9 @@ std::vector<number> nary_operation::evaluate_inputs(evaluation_context const& co
for (auto const& input : m_inputs) { for (auto const& input : m_inputs) {
auto const value = input.evaluate_output(context); auto const value = input.evaluate_output(context);
auto const bits = context.bits_override.value_or(input.bits().value_or(0)); auto const bits = context.bits_override.value_or(input.bits().value_or(0));
values.push_back((context.truncate && bits) ? this->truncate_input(0, value, bits) : value); values.push_back((context.truncate && bits != 0) ? this->truncate_input(0, value, bits) : value);
} }
return values; return values;
} }
} // namespace asic } // namespace asic
\ No newline at end of file
#ifndef ASIC_SIMULATION_OPERATION_HPP
#define ASIC_SIMULATION_OPERATION_HPP
#include "../number.hpp"
#include "../span.hpp"
#include <cstddef>
#include <cstdint>
#include <fmt/format.h>
#include <memory>
#include <optional>
#include <stdexcept>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
namespace asic {
class operation;
class signal_source;
using result_key = std::string;
using result_map = std::unordered_map<result_key, std::optional<number>>;
using delay_map = std::unordered_map<result_key, number>;
using delay_queue = std::vector<std::pair<result_key, signal_source const*>>;
struct evaluation_context final {
result_map* results = nullptr;
delay_map* delays = nullptr;
delay_queue* deferred_delays = nullptr;
std::optional<std::size_t> bits_override{};
bool truncate = false;
};
class signal_source final {
public:
signal_source() noexcept = default;
signal_source(std::shared_ptr<const operation> op, std::size_t index, std::optional<std::size_t> bits);
[[nodiscard]] explicit operator bool() const noexcept;
[[nodiscard]] std::optional<number> current_output(delay_map const& delays) const;
[[nodiscard]] number evaluate_output(evaluation_context const& context) const;
[[nodiscard]] std::optional<std::size_t> bits() const noexcept;
private:
std::shared_ptr<const operation> m_operation{};
std::size_t m_index = 0;
std::optional<std::size_t> m_bits{};
};
class operation { // NOLINT(cppcoreguidelines-special-member-functions)
public:
operation() noexcept = default;
virtual ~operation() = default;
[[nodiscard]] virtual std::size_t output_count() const noexcept = 0;
[[nodiscard]] virtual std::optional<number> current_output(std::size_t index, delay_map const& delays) const = 0;
[[nodiscard]] virtual number evaluate_output(std::size_t index, evaluation_context const& context) const = 0;
};
class abstract_operation : public operation { // NOLINT(cppcoreguidelines-special-member-functions)
public:
explicit abstract_operation(result_key key);
~abstract_operation() override = default;
[[nodiscard]] std::optional<number> current_output(std::size_t, delay_map const&) const override;
[[nodiscard]] number evaluate_output(std::size_t index, evaluation_context const& context) const override;
protected:
[[nodiscard]] virtual number evaluate_output_impl(std::size_t index, evaluation_context const& context) const = 0;
[[nodiscard]] virtual number truncate_input(std::size_t index, number value, std::size_t bits) const;
[[nodiscard]] result_key const& key_base() const;
[[nodiscard]] result_key key_of_output(std::size_t index) const;
private:
result_key m_key;
};
class unary_operation : public abstract_operation { // NOLINT(cppcoreguidelines-special-member-functions)
public:
explicit unary_operation(result_key key);
~unary_operation() override = default;
void connect(signal_source in);
protected:
[[nodiscard]] bool connected() const noexcept;
[[nodiscard]] signal_source const& input() const noexcept;
[[nodiscard]] number evaluate_input(evaluation_context const& context) const;
private:
signal_source m_in;
};
class binary_operation : public abstract_operation { // NOLINT(cppcoreguidelines-special-member-functions)
public:
explicit binary_operation(result_key key);
~binary_operation() override = default;
void connect(signal_source lhs, signal_source rhs);
protected:
[[nodiscard]] signal_source const& lhs() const noexcept;
[[nodiscard]] signal_source const& rhs() const noexcept;
[[nodiscard]] number evaluate_lhs(evaluation_context const& context) const;
[[nodiscard]] number evaluate_rhs(evaluation_context const& context) const;
private:
signal_source m_lhs;
signal_source m_rhs;
};
class nary_operation : public abstract_operation { // NOLINT(cppcoreguidelines-special-member-functions)
public:
explicit nary_operation(result_key key);
~nary_operation() override = default;
void connect(std::vector<signal_source> inputs);
protected:
[[nodiscard]] span<signal_source const> inputs() const noexcept;
[[nodiscard]] std::vector<number> evaluate_inputs(evaluation_context const& context) const;
private:
std::vector<signal_source> m_inputs{};
};
} // namespace asic
#endif // ASIC_SIMULATION_OPERATION_HPP
#include "signal_flow_graph.h" #include "signal_flow_graph.hpp"
#include "../debug.h" #include "../debug.hpp"
namespace py = pybind11; namespace py = pybind11;
...@@ -67,8 +67,8 @@ std::shared_ptr<custom_operation> signal_flow_graph_operation::add_custom_operat ...@@ -67,8 +67,8 @@ std::shared_ptr<custom_operation> signal_flow_graph_operation::add_custom_operat
std::string_view prefix, result_key key) { std::string_view prefix, result_key key) {
auto const input_count = op.attr("input_count").cast<std::size_t>(); auto const input_count = op.attr("input_count").cast<std::size_t>();
auto const output_count = op.attr("output_count").cast<std::size_t>(); auto const output_count = op.attr("output_count").cast<std::size_t>();
auto const new_op = add_operation<custom_operation>( auto new_op = add_operation<custom_operation>(
op, added, key, op.attr("evaluate_output"), op.attr("truncate_input"), output_count); op, added, std::move(key), op.attr("evaluate_output"), op.attr("truncate_input"), output_count);
auto inputs = std::vector<signal_source>{}; auto inputs = std::vector<signal_source>{};
inputs.reserve(input_count); inputs.reserve(input_count);
for (auto const i : range(input_count)) { for (auto const i : range(input_count)) {
...@@ -141,4 +141,4 @@ std::shared_ptr<operation> signal_flow_graph_operation::make_operation(pybind11: ...@@ -141,4 +141,4 @@ std::shared_ptr<operation> signal_flow_graph_operation::make_operation(pybind11:
return add_custom_operation(op, added, prefix, std::move(key)); return add_custom_operation(op, added, prefix, std::move(key));
} }
} // namespace asic } // namespace asic
\ No newline at end of file
#ifndef ASIC_SIMULATION_SIGNAL_FLOW_GRAPH_HPP
#define ASIC_SIMULATION_SIGNAL_FLOW_GRAPH_HPP
#include "../algorithm.hpp"
#include "../debug.hpp"
#include "../number.hpp"
#include "core_operations.hpp"
#include "custom_operation.hpp"
#include "operation.hpp"
#include "special_operations.hpp"
#define NOMINMAX
#include <Python.h>
#include <cstddef>
#include <fmt/format.h>
#include <functional>
#include <memory>
#include <pybind11/pybind11.h>
#include <stdexcept>
#include <string_view>
#include <unordered_map>
#include <utility>
#include <vector>
namespace asic {
class signal_flow_graph_operation final : public abstract_operation {
public:
using added_operation_cache = std::unordered_map<PyObject const*, std::shared_ptr<operation>>;
signal_flow_graph_operation(result_key key);
void create(pybind11::handle sfg, added_operation_cache& added);
[[nodiscard]] std::vector<std::shared_ptr<input_operation>> const& inputs() noexcept;
[[nodiscard]] std::size_t output_count() const noexcept final;
[[nodiscard]] number evaluate_output(std::size_t index, evaluation_context const& context) const final;
private:
[[nodiscard]] number evaluate_output_impl(std::size_t index, evaluation_context const& context) const final;
[[nodiscard]] static signal_source make_source(pybind11::handle op, std::size_t input_index, added_operation_cache& added,
std::string_view prefix);
template <typename Operation, typename... Args>
[[nodiscard]] static std::shared_ptr<Operation> add_operation(pybind11::handle op, added_operation_cache& added, Args&&... args) {
return std::static_pointer_cast<Operation>(
added.try_emplace(op.ptr(), std::make_shared<Operation>(std::forward<Args>(args)...)).first->second);
}
template <typename Operation, typename... Args>
[[nodiscard]] static std::shared_ptr<Operation> add_unary_operation(pybind11::handle op, added_operation_cache& added,
std::string_view prefix, Args&&... args) {
auto new_op = add_operation<Operation>(op, added, std::forward<Args>(args)...);
new_op->connect(make_source(op, 0, added, prefix));
return new_op;
}
template <typename Operation, typename... Args>
[[nodiscard]] static std::shared_ptr<Operation> add_binary_operation(pybind11::handle op, added_operation_cache& added,
std::string_view prefix, Args&&... args) {
auto new_op = add_operation<Operation>(op, added, std::forward<Args>(args)...);
new_op->connect(make_source(op, 0, added, prefix), make_source(op, 1, added, prefix));
return new_op;
}
[[nodiscard]] static std::shared_ptr<operation> add_signal_flow_graph_operation(pybind11::handle sfg, added_operation_cache& added,
std::string_view prefix, result_key key);
[[nodiscard]] static std::shared_ptr<custom_operation> add_custom_operation(pybind11::handle op, added_operation_cache& added,
std::string_view prefix, result_key key);
[[nodiscard]] static std::shared_ptr<operation> make_operation(pybind11::handle op, added_operation_cache& added,
std::string_view prefix);
std::vector<output_operation> m_output_operations{};
std::vector<std::shared_ptr<input_operation>> m_input_operations{};
};
} // namespace asic
#endif // ASIC_SIMULATION_SIGNAL_FLOW_GRAPH_HPP
#define NOMINMAX #include "simulation.hpp"
#include "simulation.h"
#include "../debug.h" #include "../debug.hpp"
namespace py = pybind11; namespace py = pybind11;
namespace asic { namespace asic {
simulation::simulation(pybind11::handle sfg, std::optional<std::vector<std::optional<input_provider_t>>> input_providers) simulation::simulation(pybind11::handle sfg, std::optional<std::vector<std::optional<input_provider_type>>> input_providers)
: m_sfg("") : m_input_functions(sfg.attr("input_count").cast<std::size_t>(), [](iteration_type) -> number { return number{}; }) {
, m_input_functions(sfg.attr("input_count").cast<std::size_t>(), [](iteration_t) -> number { return number{}; }) {
if (input_providers) { if (input_providers) {
this->set_inputs(std::move(*input_providers)); this->set_inputs(std::move(*input_providers));
} }
...@@ -17,29 +15,30 @@ simulation::simulation(pybind11::handle sfg, std::optional<std::vector<std::opti ...@@ -17,29 +15,30 @@ simulation::simulation(pybind11::handle sfg, std::optional<std::vector<std::opti
m_sfg.create(sfg, added); m_sfg.create(sfg, added);
} }
void simulation::set_input(std::size_t index, input_provider_t input_provider) { void simulation::set_input(std::size_t index, input_provider_type input_provider) {
if (index >= m_input_functions.size()) { if (index >= m_input_functions.size()) {
throw py::index_error{fmt::format("Input index out of range (expected 0-{}, got {})", m_input_functions.size() - 1, index)}; throw py::index_error{fmt::format("Input index out of range (expected 0-{}, got {})", m_input_functions.size() - 1, index)};
} }
if (auto* const callable = std::get_if<input_function_t>(&input_provider)) { if (auto* const callable = std::get_if<input_function_type>(&input_provider)) {
m_input_functions[index] = std::move(*callable); m_input_functions[index] = std::move(*callable);
} else if (auto* const numeric = std::get_if<number>(&input_provider)) { } else if (auto* const numeric = std::get_if<number>(&input_provider)) {
m_input_functions[index] = [value = *numeric](iteration_t) -> number { m_input_functions[index] = [value = *numeric](iteration_type) -> number {
return value; return value;
}; };
} else if (auto* const list = std::get_if<std::vector<number>>(&input_provider)) { } else if (auto* const list = std::get_if<std::vector<number>>(&input_provider)) {
if (!m_input_length) { if (!m_input_length) {
m_input_length = static_cast<iteration_t>(list->size()); m_input_length = static_cast<iteration_type>(list->size());
} else if (*m_input_length != static_cast<iteration_t>(list->size())) { } else if (*m_input_length != static_cast<iteration_type>(list->size())) {
throw py::value_error{fmt::format("Inconsistent input length for simulation (was {}, got {})", *m_input_length, list->size())}; throw py::value_error{fmt::format("Inconsistent input length for simulation (was {}, got {})", *m_input_length, list->size())};
} }
m_input_functions[index] = [values = std::move(*list)](iteration_t n) -> number { m_input_functions[index] = [values = std::move(*list)](iteration_type n) -> number {
return values.at(n); return values.at(n);
}; };
} }
} }
void simulation::set_inputs(std::vector<std::optional<input_provider_t>> input_providers) { void simulation::set_inputs(
std::vector<std::optional<input_provider_type>> input_providers) { // NOLINT(performance-unnecessary-value-param)
if (input_providers.size() != m_input_functions.size()) { if (input_providers.size() != m_input_functions.size()) {
throw py::value_error{fmt::format( throw py::value_error{fmt::format(
"Wrong number of inputs supplied to simulation (expected {}, got {})", m_input_functions.size(), input_providers.size())}; "Wrong number of inputs supplied to simulation (expected {}, got {})", m_input_functions.size(), input_providers.size())};
...@@ -55,7 +54,7 @@ std::vector<number> simulation::step(bool save_results, std::optional<std::size_ ...@@ -55,7 +54,7 @@ std::vector<number> simulation::step(bool save_results, std::optional<std::size_
return this->run_for(1, save_results, bits_override, truncate); return this->run_for(1, save_results, bits_override, truncate);
} }
std::vector<number> simulation::run_until(iteration_t iteration, bool save_results, std::optional<std::size_t> bits_override, std::vector<number> simulation::run_until(iteration_type iteration, bool save_results, std::optional<std::size_t> bits_override,
bool truncate) { bool truncate) {
auto result = std::vector<number>{}; auto result = std::vector<number>{};
while (m_iteration < iteration) { while (m_iteration < iteration) {
...@@ -100,9 +99,9 @@ std::vector<number> simulation::run_until(iteration_t iteration, bool save_resul ...@@ -100,9 +99,9 @@ std::vector<number> simulation::run_until(iteration_t iteration, bool save_resul
return result; return result;
} }
std::vector<number> simulation::run_for(iteration_t iterations, bool save_results, std::optional<std::size_t> bits_override, std::vector<number> simulation::run_for(iteration_type iterations, bool save_results, std::optional<std::size_t> bits_override,
bool truncate) { bool truncate) {
if (iterations > std::numeric_limits<iteration_t>::max() - m_iteration) { if (iterations > std::numeric_limits<iteration_type>::max() - m_iteration) {
throw py::value_error("Simulation iteration type overflow!"); throw py::value_error("Simulation iteration type overflow!");
} }
return this->run_until(m_iteration + iterations, save_results, bits_override, truncate); return this->run_until(m_iteration + iterations, save_results, bits_override, truncate);
...@@ -115,7 +114,7 @@ std::vector<number> simulation::run(bool save_results, std::optional<std::size_t ...@@ -115,7 +114,7 @@ std::vector<number> simulation::run(bool save_results, std::optional<std::size_t
throw py::index_error{"Tried to run unlimited simulation"}; throw py::index_error{"Tried to run unlimited simulation"};
} }
iteration_t simulation::iteration() const noexcept { iteration_type simulation::iteration() const noexcept {
return m_iteration; return m_iteration;
} }
......
#ifndef ASIC_SIMULATION_OOP_HPP
#define ASIC_SIMULATION_OOP_HPP
#include "../number.hpp"
#include "core_operations.hpp"
#include "custom_operation.hpp"
#include "operation.hpp"
#include "signal_flow_graph.hpp"
#include "special_operations.hpp"
#define NOMINMAX
#include <cstddef>
#include <cstdint>
#include <fmt/format.h>
#include <functional>
#include <limits>
#include <memory>
#include <optional>
#include <pybind11/functional.h>
#include <pybind11/numpy.h>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <string_view>
#include <unordered_map>
#include <utility>
#include <variant>
#include <vector>
namespace asic {
using iteration_type = std::uint32_t;
using result_array_map = std::unordered_map<std::string, std::vector<number>>;
using input_function_type = std::function<number(iteration_type)>;
using input_provider_type = std::variant<number, std::vector<number>, input_function_type>;
class simulation final {
public:
simulation(pybind11::handle sfg, std::optional<std::vector<std::optional<input_provider_type>>> input_providers = std::nullopt);
void set_input(std::size_t index, input_provider_type input_provider);
void set_inputs(std::vector<std::optional<input_provider_type>> input_providers);
[[nodiscard]] std::vector<number> step(bool save_results, std::optional<std::size_t> bits_override, bool truncate);
[[nodiscard]] std::vector<number> run_until(iteration_type iteration, bool save_results, std::optional<std::size_t> bits_override,
bool truncate);
[[nodiscard]] std::vector<number> run_for(iteration_type iterations, bool save_results, std::optional<std::size_t> bits_override,
bool truncate);
[[nodiscard]] std::vector<number> run(bool save_results, std::optional<std::size_t> bits_override, bool truncate);
[[nodiscard]] iteration_type iteration() const noexcept;
[[nodiscard]] pybind11::dict results() const noexcept;
void clear_results() noexcept;
void clear_state() noexcept;
private:
signal_flow_graph_operation m_sfg{""};
result_array_map m_results{};
delay_map m_delays{};
iteration_type m_iteration = 0;
std::optional<iteration_type> m_input_length{};
std::vector<input_function_type> m_input_functions;
};
} // namespace asic
#endif // ASIC_SIMULATION_OOP_HPP
#include "special_operations.h" #include "special_operations.hpp"
#include "../debug.h" #include "../debug.hpp"
namespace asic { namespace asic {
......
#ifndef ASIC_SIMULATION_SPECIAL_OPERATIONS_HPP
#define ASIC_SIMULATION_SPECIAL_OPERATIONS_HPP
#include "../debug.hpp"
#include "../number.hpp"
#include "operation.hpp"
#include <cassert>
#include <cstddef>
#include <utility>
namespace asic {
class input_operation final : public unary_operation {
public:
explicit input_operation(result_key key);
[[nodiscard]] std::size_t output_count() const noexcept final;
[[nodiscard]] number value() const noexcept;
void value(number value) noexcept;
private:
[[nodiscard]] number evaluate_output_impl(std::size_t index, evaluation_context const& context) const final;
number m_value{};
};
class output_operation final : public unary_operation {
public:
explicit output_operation(result_key key);
[[nodiscard]] std::size_t output_count() const noexcept final;
private:
[[nodiscard]] number evaluate_output_impl(std::size_t index, evaluation_context const& context) const final;
};
class delay_operation final : public unary_operation {
public:
delay_operation(result_key key, number initial_value);
[[nodiscard]] std::size_t output_count() const noexcept final;
[[nodiscard]] std::optional<number> current_output(std::size_t index, delay_map const& delays) const final;
[[nodiscard]] number evaluate_output(std::size_t index, evaluation_context const& context) const final;
private:
[[nodiscard]] number evaluate_output_impl(std::size_t index, evaluation_context const& context) const final;
number m_initial_value;
};
} // namespace asic
#endif // ASIC_SIMULATION_SPECIAL_OPERATIONS_HPP
...@@ -29,7 +29,7 @@ class CMakeBuild(build_ext): ...@@ -29,7 +29,7 @@ class CMakeBuild(build_ext):
os.path.dirname(self.get_ext_fullpath(ext.name))) os.path.dirname(self.get_ext_fullpath(ext.name)))
cmake_configure_argv = [ cmake_configure_argv = [
CMAKE_EXE, ext.sourcedir, CMAKE_EXE, ext.sourcedir,
"-DASIC_BUILDING_PYTHON_DISTRIBUTION=true", "-DASIC_BUILDING_PYTHON_DISTRIBUTION=ON",
"-DCMAKE_BUILD_TYPE=" + cmake_build_type, "-DCMAKE_BUILD_TYPE=" + cmake_build_type,
"-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=" + cmake_output_dir, "-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=" + cmake_output_dir,
"-DPYTHON_EXECUTABLE=" + sys.executable, "-DPYTHON_EXECUTABLE=" + sys.executable,
...@@ -80,7 +80,7 @@ setuptools.setup( ...@@ -80,7 +80,7 @@ setuptools.setup(
"pybind11>=2.3.0", "pybind11>=2.3.0",
"pyside2", "pyside2",
"qtpy", "qtpy",
"graphviz", "graphviz<=0.17",
"matplotlib", "matplotlib",
"scipy" "scipy"
], ],
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment