├── .gitignore ├── docs ├── lsim_main.png ├── lsim_small.png ├── building.md ├── todo.md ├── using_python_bindings.md └── using_lsim_gui.md ├── libs └── cute │ └── cute_files.cpp ├── src ├── gui │ ├── gui_defs.h │ ├── configuration.h │ ├── ui_window_main.h │ ├── component_draw.h │ ├── ui_popup_files.h │ ├── imgui │ │ ├── LICENSE.txt │ │ ├── imgui_impl_sdl.h │ │ ├── imgui_impl_opengl3.h │ │ └── imconfig.h │ ├── component_icon.h │ ├── imgui_ex.h │ ├── colors.h │ ├── ui_panel_library.cpp │ ├── ui_context.h │ ├── ui_panel_circuit.cpp │ ├── component_icon.cpp │ ├── shell_minimal.html │ ├── component_widget.h │ ├── imgui_ex.cpp │ ├── component_widget.cpp │ ├── ui_context.cpp │ ├── ui_popup_files.cpp │ ├── ui_window_main.cpp │ ├── ui_panel_component.cpp │ ├── circuit_editor.h │ └── main_emscripten.cpp ├── error.h ├── error.cpp ├── serialize.h ├── load_logisim.h ├── sim_functions.cpp ├── bench │ ├── bench_utils.py │ ├── bench_shifter.py │ ├── bench_compare.py │ ├── bench_adder.py │ ├── bench_counters.py │ └── bench_memory.py ├── tools │ ├── rom_led_hex_digit.py │ ├── rom_8bit_common.py │ ├── rom_led_decimal_8b.py │ ├── speedtest │ │ └── speedtest_main.cpp │ ├── rom_8bit_program.py │ └── rom_8bit_ctrl.py ├── sim_functions.h ├── std_helper.h ├── lsim_context.h ├── sim_circuit.h ├── model_circuit_library.h ├── sim_types.h ├── algebra.h ├── model_circuit_library.cpp ├── sim_component.h ├── algebra.cpp ├── sim_gates.cpp ├── model_component.h ├── lsim_context.cpp ├── sim_component.cpp ├── sim_various.cpp ├── model_wire.h ├── model_property.h ├── model_circuit.h ├── simulator.h ├── model_component.cpp └── model_property.cpp ├── tests ├── test_main.cpp ├── test_algebra.cpp └── test_extra.cpp ├── .gitmodules ├── .clang-tidy ├── LICENSE ├── .vscode └── launch.json ├── README.md ├── .github └── workflows │ └── cmake.yml ├── examples └── tristate.circ └── CMakeLists.txt /.gitignore: -------------------------------------------------------------------------------- 1 | _build*/ 2 | build/ 3 | imgui.ini 4 | .vscode/ipch/ 5 | __pycache__/ 6 | -------------------------------------------------------------------------------- /docs/lsim_main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohanSmet/lsim/HEAD/docs/lsim_main.png -------------------------------------------------------------------------------- /docs/lsim_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohanSmet/lsim/HEAD/docs/lsim_small.png -------------------------------------------------------------------------------- /libs/cute/cute_files.cpp: -------------------------------------------------------------------------------- 1 | #define CUTE_FILES_IMPLEMENTATION 2 | #include "cute_files.h" -------------------------------------------------------------------------------- /src/gui/gui_defs.h: -------------------------------------------------------------------------------- 1 | // configuration.h - Johan Smet - BSD-3-Clause (see LICENSE) 2 | 3 | 4 | -------------------------------------------------------------------------------- /tests/test_main.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do this in one cpp file 2 | #include "catch.hpp" 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "libs/pugixml"] 2 | path = libs/pugixml 3 | url = https://github.com/zeux/pugixml.git 4 | [submodule "libs/pybind11"] 5 | path = libs/pybind11 6 | url = https://github.com/pybind/pybind11 7 | branch = stable 8 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | Checks: 'clang-diagnostic-*,clang-analyzer-*,bugprone-*,modernize-*,performance-*,readability-*,portability-*,misc-*' 3 | WarningsAsErrors: '' 4 | HeaderFilterRegex: 'lsim\/src\/[^\/]*\.h$|lsim\/src\/gui\/[^\/]*\.h$' 5 | ... 6 | -------------------------------------------------------------------------------- /src/error.h: -------------------------------------------------------------------------------- 1 | // error.h - Johan Smet - BSD-3-Clause (see LICENSE) 2 | // 3 | // error message handling 4 | 5 | #ifndef LSIM_ERROR_H 6 | #define LSIM_ERROR_H 7 | 8 | #define ERROR_MSG(fmt, ...) error_msg(__FILE__, (fmt), __VA_ARGS__) 9 | 10 | void error_msg(const char *file, const char *fmt, ...); 11 | 12 | #endif // LSIM_ERROR_H 13 | -------------------------------------------------------------------------------- /src/gui/configuration.h: -------------------------------------------------------------------------------- 1 | // configuration.h - Johan Smet - BSD-3-Clause (see LICENSE) 2 | 3 | #ifndef LSIM_GUI_CONFIGURATION_H 4 | #define LSIM_GUI_CONFIGURATION_H 5 | 6 | namespace lsim { 7 | 8 | namespace gui { 9 | 10 | constexpr float GRID_SIZE = 10.0f; 11 | 12 | } // namespace gui 13 | 14 | } // namespace lsim 15 | 16 | 17 | #endif // LSIM_GUI_CONFIGURATION_H 18 | -------------------------------------------------------------------------------- /src/gui/ui_window_main.h: -------------------------------------------------------------------------------- 1 | #ifndef LSIM_GUI_MAIN_GUI_H 2 | #define LSIM_GUI_MAIN_GUI_H 3 | 4 | namespace lsim { 5 | 6 | class SimCircuit; 7 | class ModelComponent; 8 | 9 | namespace gui { 10 | 11 | void main_window_setup(const char *circuit_file); 12 | void main_window_update(); 13 | 14 | } // namespace gui 15 | 16 | } // namespace lsim 17 | 18 | #endif // LSIM_GUI_MAIN_GUI_H -------------------------------------------------------------------------------- /src/error.cpp: -------------------------------------------------------------------------------- 1 | // error.cpp - Johan Smet - BSD-3-Clause (see LICENSE) 2 | // 3 | // error message handling 4 | 5 | #include "error.h" 6 | #include 7 | #include 8 | 9 | void error_msg(const char *file, const char *fmt, ...) { 10 | // just dump errors to stderr for now 11 | va_list args; 12 | va_start(args, fmt); 13 | fprintf(stderr, "ERROR (%s) : ", file); 14 | vfprintf(stderr, fmt, args); 15 | fprintf(stderr, "\n"); 16 | va_end(args); 17 | } -------------------------------------------------------------------------------- /src/serialize.h: -------------------------------------------------------------------------------- 1 | // serialize.h - Johan Smet - BSD-3-Clause (see LICENSE) 2 | // 3 | // Serialize / deserialize circuits & components 4 | 5 | #ifndef LSIM_SERIALISE_H 6 | #define LSIM_SERIALISE_H 7 | 8 | namespace lsim { 9 | 10 | class LSimContext; 11 | class ModelCircuitLibrary; 12 | 13 | bool serialize_library(LSimContext *context, ModelCircuitLibrary *lib, const char *filename); 14 | bool deserialize_library(LSimContext *context, ModelCircuitLibrary *lib, const char *filename); 15 | 16 | } // namespace lsim 17 | 18 | #endif // LSIM_SERIALIZE_H -------------------------------------------------------------------------------- /src/gui/component_draw.h: -------------------------------------------------------------------------------- 1 | // component_draw.h - Johan Smet - BSD-3-Clause (see LICENSE) 2 | // 3 | // functions to materialize components and define custom draw functions 4 | 5 | #ifndef LSIM_GUI_COMPONENT_DRAW_H 6 | #define LSIM_GUI_COMPONENT_DRAW_H 7 | 8 | namespace lsim { 9 | 10 | namespace gui { 11 | 12 | void component_register_basic(); 13 | void component_register_extra(); 14 | void component_register_gates(); 15 | void component_register_input_output(); 16 | 17 | } // namespace lsim::gui 18 | 19 | } // namespace lsim 20 | 21 | #endif // LSIM_GUI_COMPONENT_DRAW_H -------------------------------------------------------------------------------- /src/load_logisim.h: -------------------------------------------------------------------------------- 1 | // load_logisim.cpp - Johan Smet - BSD-3-Clause (see LICENSE) 2 | // 3 | // Load circuit from a LogiSim circuit 4 | 5 | #ifndef LSIM_LOAD_LOGISIM_H 6 | #define LSIM_LOAD_LOGISIM_H 7 | 8 | // includes 9 | #include 10 | 11 | namespace lsim { 12 | 13 | // forward declarations 14 | class Circuit; 15 | class LSimContext; 16 | 17 | // interface 18 | bool load_logisim(LSimContext *lsim_context, const char *filename); 19 | bool load_logisim(LSimContext *lsim_context, const char *data, size_t len); 20 | 21 | } // namespace lsim 22 | 23 | #endif // LSIM_LOAD_LOGISIM_H -------------------------------------------------------------------------------- /src/sim_functions.cpp: -------------------------------------------------------------------------------- 1 | // sim_functions.cpp - Johan Smet - BSD-3-Clause (see LICENSE) 2 | // 3 | // simulation functions for basic logic components 4 | 5 | #include "sim_functions.h" 6 | #include "simulator.h" 7 | 8 | #include 9 | #include 10 | 11 | namespace lsim { 12 | 13 | // prototypes for registration functions defined in separate modules. (I guess somebody is too lazy to put them in a header?) 14 | void sim_register_gate_functions(Simulator *); 15 | void sim_register_various_functions(Simulator *); 16 | 17 | void sim_register_component_functions(Simulator *sim) { 18 | sim_register_gate_functions(sim); 19 | sim_register_various_functions(sim); 20 | } 21 | 22 | } // namespace lsim -------------------------------------------------------------------------------- /src/gui/ui_popup_files.h: -------------------------------------------------------------------------------- 1 | // ui_popup_files.h - Johan Smet - BSD-3-Clause (see LICENSE) 2 | // 3 | // file related popups 4 | 5 | #ifndef LSIM_GUI_UI_POPUP_FILES_H 6 | #define LSIM_GUI_UI_POPUP_FILES_H 7 | 8 | #include 9 | #include 10 | 11 | namespace lsim { 12 | 13 | class LSimContext; 14 | 15 | namespace gui { 16 | 17 | using on_select_func_t = std::function; 18 | 19 | void ui_file_selector_open(LSimContext *context, on_select_func_t on_select); 20 | void ui_file_selector_define(); 21 | 22 | void ui_filename_entry_open(on_select_func_t on_close); 23 | void ui_filename_entry_define(); 24 | 25 | } // namespace lsim::gui 26 | 27 | } // namespace lsim 28 | 29 | 30 | 31 | #endif // LSIM_GUI_UI_POPUP_FILES_H -------------------------------------------------------------------------------- /src/gui/imgui/LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2019 Omar Cornut 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/gui/component_icon.h: -------------------------------------------------------------------------------- 1 | // component_icon.h - Johan Smet - BSD-3-Clause (see LICENSE) 2 | 3 | #ifndef LSIM_GUI_COMPONENT_ICON_H 4 | #define LSIM_GUI_COMPONENT_ICON_H 5 | 6 | #include "algebra.h" 7 | #include "std_helper.h" 8 | 9 | struct ImDrawList; 10 | 11 | namespace lsim { 12 | 13 | namespace gui { 14 | 15 | class ComponentIcon { 16 | public: 17 | ComponentIcon(const char* data, size_t len); 18 | void draw(Transform transform, Point draw_size, ImDrawList* draw_list, size_t line_width, uint32_t color) const; 19 | void draw(Point origin, Point draw_size, ImDrawList* draw_list, size_t line_width, uint32_t color) const; 20 | static ComponentIcon* cache(uint32_t id, const char* data, size_t len); 21 | static ComponentIcon* cached(uint32_t id); 22 | private: 23 | using bezier_t = std::array; 24 | using bezier_container_t = std::vector; 25 | using icon_lut_t = std::unordered_map >; 26 | private: 27 | bezier_container_t m_curves; 28 | Point m_size; 29 | 30 | static icon_lut_t m_icon_cache; 31 | }; 32 | 33 | } // namespace lsim::gui 34 | 35 | } // namespace lsim 36 | 37 | #endif // LSIM_GUI_COMPONENT_ICON_H 38 | -------------------------------------------------------------------------------- /src/gui/imgui_ex.h: -------------------------------------------------------------------------------- 1 | // imgui_ex.h - Johan Smet - BSD-3-Clause (see LICENSE) 2 | // 3 | // QOL extensions for imgui 4 | 5 | #include "imgui/imgui.h" 6 | #include "algebra.h" 7 | #include 8 | 9 | namespace ImGuiEx { 10 | 11 | enum TextAlignHor { 12 | TAH_LEFT, 13 | TAH_RIGHT, 14 | TAH_CENTER 15 | }; 16 | 17 | enum TextAlignVer { 18 | TAV_TOP, 19 | TAV_BOTTOM, 20 | TAV_CENTER 21 | }; 22 | 23 | void Text(ImVec2 at, const char *text, TextAlignHor align_hor = TAH_LEFT, TextAlignVer align_ver = TAV_TOP); 24 | void TextNoClip(ImVec2 at, const char *text, ImU32 col, TextAlignHor align_hor = TAH_LEFT, TextAlignVer align_ver = TAV_TOP); 25 | inline void Text(ImVec2 at, const std::string &text, TextAlignHor align_hor = TAH_LEFT, TextAlignVer align_ver = TAV_TOP) { 26 | Text(at, text.c_str(), align_hor, align_ver); 27 | } 28 | inline void TextNoClip(ImVec2 at, const std::string &text, ImU32 col, TextAlignHor align_hor = TAH_LEFT, TextAlignVer align_ver = TAV_TOP) { 29 | TextNoClip(at, text.c_str(), col, align_hor, align_ver); 30 | } 31 | void RectFilled(ImVec2 p1, ImVec2 p2, ImU32 col); 32 | 33 | void TransformStart(); 34 | void TransformEnd(lsim::Transform transform); 35 | 36 | 37 | } // namespace ImGuiEx -------------------------------------------------------------------------------- /docs/building.md: -------------------------------------------------------------------------------- 1 | # Building LSim 2 | 3 | ## Requirements 4 | 5 | - a C++14 compatible compiler 6 | - CMake (version 3.13 or newer) 7 | - (optional) Emscripten to build the browser front-end 8 | - SDL2 development libraries have to be available on your system 9 | 10 | ## Building the native executables 11 | 12 | LSim uses the CMake build system, all you need is the traditional CMake workflow. On Linux it would be something like this: 13 | 14 | ```bash 15 | git clone --recursive https://github.com/JohanSmet/lsim.git 16 | cd lsim 17 | cmake -B _build 18 | cmake --build _build 19 | ``` 20 | 21 | Specify -DPYTHON_BINDINGS=ON with the cmake command to build the Python bindings. 22 | 23 | ## Building the wasm executables 24 | 25 | Requires a working Emscripten installation. 26 | 27 | ```bash 28 | git clone --recursive https://github.com/JohanSmet/lsim.git 29 | cd lsim 30 | emcmake cmake -B _build_wasm 31 | cmake --build _build_wasm 32 | ``` 33 | 34 | This builds the WebAssembly and glue-files. Copy lsim.* to a location that is accessible from your webserver. 35 | 36 | ## Continuous Integration 37 | 38 | LSim uses GitHub workflows to automatically run and test a build for each supported workflow. 39 | The [configuration file](../.github/workflows/cmake.yml) is also a good reference for, tested, build instructions. 40 | -------------------------------------------------------------------------------- /tests/test_algebra.cpp: -------------------------------------------------------------------------------- 1 | #include "catch.hpp" 2 | #include "algebra.h" 3 | 4 | using namespace lsim; 5 | using namespace Catch::literals; 6 | 7 | TEST_CASE ("Basic transform operations", "[algebra]") { 8 | 9 | auto transform_is_identity = [](const Transform &tf) { 10 | REQUIRE(tf.el(0,0) == 1.0_a); 11 | REQUIRE(tf.el(0,1) == 0.0_a); 12 | REQUIRE(tf.el(1,0) == 0.0_a); 13 | REQUIRE(tf.el(1,1) == 1.0_a); 14 | REQUIRE(tf.el(2,0) == 0.0_a); 15 | REQUIRE(tf.el(2,1) == 0.0_a); 16 | }; 17 | 18 | // default constructor 19 | Transform trans_1; 20 | transform_is_identity(trans_1); 21 | 22 | // translate 23 | trans_1.translate({5.0f, -7.0f}); 24 | REQUIRE(trans_1.el(0,0) == 1.0_a); 25 | REQUIRE(trans_1.el(0,1) == 0.0_a); 26 | REQUIRE(trans_1.el(1,0) == 0.0_a); 27 | REQUIRE(trans_1.el(1,1) == 1.0_a); 28 | REQUIRE(trans_1.el(2,0) == 5.0_a); 29 | REQUIRE(trans_1.el(2,1) == -7.0_a); 30 | 31 | // copy constructor 32 | Transform trans_2(trans_1); 33 | REQUIRE(trans_2.el(0,0) == 1.0_a); 34 | REQUIRE(trans_2.el(0,1) == 0.0_a); 35 | REQUIRE(trans_2.el(1,0) == 0.0_a); 36 | REQUIRE(trans_2.el(1,1) == 1.0_a); 37 | REQUIRE(trans_2.el(2,0) == 5.0_a); 38 | REQUIRE(trans_2.el(2,1) == -7.0_a); 39 | 40 | // reset 41 | trans_1.reset(); 42 | transform_is_identity(trans_1); 43 | } -------------------------------------------------------------------------------- /src/bench/bench_utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | count_check = 0 4 | count_failure = 0 5 | 6 | def CHECK(was, expected, op_str): 7 | global count_check, count_failure 8 | count_check = count_check + 1 9 | if was != expected: 10 | count_failure = count_failure + 1 11 | print("FAILURE: {} = {} (expected {})".format(op_str, was, expected)) 12 | 13 | def print_stats(): 14 | global count_check, count_failure 15 | print("======================================================") 16 | if count_failure == 0: 17 | print("All tests passed ({} checks executed)".format(count_check)) 18 | else: 19 | print("{} out of {} checks failed!".format(count_failure, count_check)) 20 | 21 | def instantiate_circuit(lsim, name): 22 | circuit_desc = lsim.user_library().circuit_by_name(name) 23 | circuit = circuit_desc.instantiate(lsim.sim()) 24 | lsim.sim().init() 25 | return circuit 26 | 27 | def run_thruth_table(lsim, circuit_name, truth_table): 28 | print ("* Testing {}".format(circuit_name)) 29 | 30 | circuit = instantiate_circuit(lsim, circuit_name) 31 | for test in truth_table: 32 | for v in test[0].items(): 33 | circuit.write_port(v[0], v[1]) 34 | lsim.sim().run_until_stable(5) 35 | for v in test[1].items(): 36 | CHECK(circuit.read_port(v[0]), v[1], "{}: {}".format(circuit_name, test[0])) -------------------------------------------------------------------------------- /src/tools/rom_led_hex_digit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # rom_led_hex.py: ROM that replaces the logic to display a single hexadecimal digit 4 | # - input : 4 bits (the number to display) 5 | # - output : 8 bits (the segments to light up) 6 | # 7 | # +-a-+ 8 | # f b 9 | # +-g-+ 10 | # e c 11 | # +-d-+ dp 12 | # 13 | 14 | import struct 15 | 16 | 17 | rom_data = [ 18 | # .gfedcba 19 | 0b00111111, # 0 20 | 0b00000110, # 1 21 | 0b01011011, # 2 22 | 0b01001111, # 3 23 | 0b01100110, # 4 24 | 0b01101101, # 5 25 | 0b01111101, # 6 26 | 0b00000111, # 7 27 | 0b01111111, # 8 28 | 0b01101111, # 9 29 | 0b01110111, # A 30 | 0b01111100, # B 31 | 0b00111001, # C 32 | 0b01011110, # D 33 | 0b01111001, # E 34 | 0b01110001 # F 35 | # .gfedcba 36 | ] 37 | 38 | def write_binary(filename, data_list, word_size): 39 | assert word_size in [8,16,32], "word_size should be 8/16/32" 40 | 41 | # pack data 42 | data_fmt = "=%sl" if word_size == 32 else "=%sh" if word_size == 16 else "=%sB" 43 | data_bin = struct.pack(data_fmt % len(data_list), *data_list) 44 | 45 | # write data 46 | try: 47 | with open(filename, 'wb') as f: 48 | f.write(data_bin) 49 | except IOError as err: 50 | print(err) 51 | exit(-1) 52 | 53 | def main(): 54 | write_binary("led_hex.bin", rom_data, 8) 55 | 56 | if __name__ == "__main__": 57 | main() -------------------------------------------------------------------------------- /src/bench/bench_shifter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import lsimpy 4 | from bench_utils import * 5 | 6 | def main(): 7 | lsim = lsimpy.LSimContext() 8 | lsim.add_folder("examples", "../../examples") 9 | sim = lsim.sim() 10 | 11 | if (not lsim.load_user_library("examples/shifter.lsim")): 12 | print("Unable to load circuit\n") 13 | exit(-1) 14 | 15 | circuit_desc = lsim.user_library().circuit_by_name('barrel_8bit') 16 | 17 | pin_SHL = circuit_desc.port_by_name("Shl") 18 | pin_A = [circuit_desc.port_by_name(f"A[{i:}]") for i in range(0,8)] 19 | pin_S = [circuit_desc.port_by_name(f"S[{i:}]") for i in range(0,4)] 20 | pin_Y = [circuit_desc.port_by_name(f"Y[{i:}]") for i in range(0,8)] 21 | 22 | circuit = circuit_desc.instantiate(sim) 23 | sim.init() 24 | 25 | for shl in range(0, 2): 26 | circuit.write_pin(pin_SHL, lsimpy.ValueTrue if shl else lsimpy.ValueFalse) 27 | for a in range (0, 2**8): 28 | circuit.write_byte(pin_A, a) 29 | for s in range (0, 2**3): 30 | circuit.write_nibble(pin_S, s) 31 | sim.run_until_stable(5) 32 | 33 | result = circuit.read_byte(pin_Y) 34 | expected = (a << s if shl else a >> s) & (2**8-1) 35 | CHECK(result, expected, "SHL({}), A({}), S({})".format(shl,a,s)) 36 | 37 | print_stats() 38 | 39 | if __name__ == "__main__": 40 | main() 41 | -------------------------------------------------------------------------------- /src/gui/colors.h: -------------------------------------------------------------------------------- 1 | // colors.h - Johan Smet - BSD-3-Clause (see LICENSE) 2 | // 3 | // Commonly used colors 4 | 5 | #ifndef LSIM_GUI_COLORS_H 6 | #define LSIM_GUI_COLORS_H 7 | 8 | #include "imgui/imgui.h" 9 | 10 | constexpr const auto COLOR_COMPONENT_BORDER = IM_COL32(200, 200, 200, 255); 11 | constexpr const auto COLOR_COMPONENT_BORDER_DRAGGING = IM_COL32(100, 100, 100, 255); 12 | constexpr const auto COLOR_COMPONENT_ICON = COLOR_COMPONENT_BORDER; 13 | 14 | constexpr const auto COLOR_COMPONENT_SELECTED = IM_COL32(100, 100, 0, 255); 15 | 16 | constexpr const auto COLOR_CONNECTION_FALSE = IM_COL32(0, 75, 0, 255); 17 | constexpr const auto COLOR_CONNECTION_TRUE = IM_COL32(0, 175, 0, 255); 18 | constexpr const auto COLOR_CONNECTION_UNDEFINED = IM_COL32(120, 120, 120, 255); 19 | constexpr const auto COLOR_CONNECTION_ERROR = IM_COL32(200, 0, 0, 255); 20 | constexpr const auto COLOR_CONNECTION_DIRTY = IM_COL32(150, 150, 150, 175); 21 | 22 | constexpr const auto COLOR_ENDPOINT = IM_COL32(150, 150, 150, 255); 23 | constexpr const auto COLOR_ENDPOINT_HOVER = IM_COL32(255, 255, 0, 200); 24 | 25 | const ImU32 COLOR_CONNECTION[] = { 26 | COLOR_CONNECTION_FALSE, 27 | COLOR_CONNECTION_TRUE, 28 | COLOR_CONNECTION_UNDEFINED, 29 | COLOR_CONNECTION_ERROR 30 | }; 31 | 32 | constexpr const auto COLOR_WIRE_SELECTED = IM_COL32(100, 100, 0, 255); 33 | 34 | constexpr const auto COLOR_GRID_LINE = IM_COL32(200, 200, 200, 40); 35 | 36 | #endif // LSIM_GUI_COLORS_H -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Johan Smet 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 | 7 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | 9 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 10 | 11 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 12 | -------------------------------------------------------------------------------- /src/bench/bench_compare.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import lsimpy 4 | from bench_utils import * 5 | 6 | def main(): 7 | lsim = lsimpy.LSimContext() 8 | lsim.add_folder("examples", "../../examples") 9 | sim = lsim.sim() 10 | 11 | if (not lsim.load_user_library("examples/compare.lsim")): 12 | print("Unable to load circuit\n") 13 | exit(-1) 14 | 15 | circuit_desc = lsim.user_library().circuit_by_name("comp_8bit") 16 | 17 | pin_A = [circuit_desc.port_by_name(f"A[{i:}]") for i in range(0,8)] 18 | pin_B = [circuit_desc.port_by_name(f"B[{i:}]") for i in range(0,8)] 19 | pin_LT = circuit_desc.port_by_name("LT") 20 | pin_EQ = circuit_desc.port_by_name("EQ") 21 | pin_GT = circuit_desc.port_by_name("GT") 22 | 23 | circuit = circuit_desc.instantiate(sim) 24 | sim.init() 25 | 26 | for a in range(0, 2**8): 27 | circuit.write_byte(pin_A, a) 28 | for b in range(0, 2**8): 29 | circuit.write_byte(pin_B, b) 30 | sim.run_until_stable(5) 31 | expected_LT = lsimpy.ValueTrue if a < b else lsimpy.ValueFalse 32 | expected_EQ = lsimpy.ValueTrue if a == b else lsimpy.ValueFalse 33 | expected_GT = lsimpy.ValueTrue if a > b else lsimpy.ValueFalse 34 | CHECK(circuit.read_pin(pin_LT), expected_LT, "{} < {}".format(a, b)) 35 | CHECK(circuit.read_pin(pin_EQ), expected_EQ, "{} == {}".format(a, b)) 36 | CHECK(circuit.read_pin(pin_GT), expected_GT, "{} > {}".format(a, b)) 37 | 38 | print_stats() 39 | 40 | 41 | if __name__ == "__main__": 42 | main() 43 | -------------------------------------------------------------------------------- /src/bench/bench_adder.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import lsimpy 4 | from bench_utils import * 5 | 6 | def main(): 7 | lsim = lsimpy.LSimContext() 8 | sim = lsim.sim() 9 | 10 | if (not lsim.load_user_library("../../examples/adder.lsim")): 11 | print("Unable to load circuit\n") 12 | exit(-1) 13 | 14 | circuit_desc = lsim.user_library().circuit_by_name("adder_4bit") 15 | 16 | pin_Ci = circuit_desc.port_by_name("Ci") 17 | pin_A = [circuit_desc.port_by_name(f"A{i:}") for i in range(0,4)] 18 | pin_B = [circuit_desc.port_by_name(f"B{i:}") for i in range(0,4)] 19 | pin_Y = [circuit_desc.port_by_name(f"Y{i:}") for i in range(0,4)] 20 | pin_Co = circuit_desc.port_by_name("Co") 21 | 22 | circuit = circuit_desc.instantiate(sim) 23 | sim.init() 24 | 25 | for ci in [0, 1]: 26 | circuit.write_pin(pin_Ci, lsimpy.ValueTrue if ci else lsimpy.ValueFalse) 27 | 28 | for a in range(0, 2**4): 29 | circuit.write_nibble(pin_A, a) 30 | 31 | for b in range(0, 2**4): 32 | circuit.write_nibble(pin_B, b) 33 | sim.run_until_stable(5) 34 | 35 | expected_y = a + b + ci 36 | expected_co = 0 37 | if expected_y >= 2**4: 38 | expected_co = 1 39 | expected_y = expected_y - 2**4 40 | 41 | CHECK(circuit.read_pin(pin_Co), expected_co, "{} + {} + {}".format(ci, a, b)) 42 | CHECK(circuit.read_nibble(pin_Y), expected_y, "{} + {} + {}".format(ci, a, b)) 43 | 44 | print_stats() 45 | 46 | if __name__ == "__main__": 47 | main() -------------------------------------------------------------------------------- /src/tools/rom_8bit_common.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # rom_8bit_common.py - Johan Smet - BSD-3-Clause (see LICENSE) 4 | # common definitions for the minimal 8-bit computer 5 | 6 | import struct 7 | 8 | # OPCODES 9 | OPCODE_NOP = 0b00000 # (0) No Operation 10 | OPCODE_LDA = 0b00001 # (1) Load Register-A from memory 11 | OPCODE_LDB = 0b00010 # (2) Load Register-B from memory 12 | OPCODE_LTA = 0b00011 # (3) Load literal into Register-A 13 | OPCODE_LTB = 0b00100 # (4) Load literal into Register-B 14 | OPCODE_STA = 0b00101 # (5) Store from Register-A to memory 15 | OPCODE_STB = 0b00110 # (6) Store from Register-B to memory 16 | OPCODE_PRA = 0b00111 # (7) Output Register-A to display 17 | OPCODE_JMP = 0b01000 # (8) Jump to address 18 | OPCODE_JZ = 0b01001 # (9) Conditional Jump (zero-flag set) to address 19 | OPCODE_JC = 0b01010 # (10) Conditional Jump (carry-flag SET) to address 20 | OPCODE_HLT = 0b01111 # (15) Halt CPU 21 | 22 | OPCODE_ADD = 0b10000 # (16) Add register-B to register-A (result in register-A) 23 | OPCODE_SUB = 0b10001 # (17) Subtract register-B from register-A (result in register-A) 24 | 25 | def write_binary(filename, data_list, word_size): 26 | assert word_size in [8,16,32], "word_size should be 8/16/32" 27 | 28 | # pack data 29 | data_fmt = "=%sL" if word_size == 32 else "=%sH" if word_size == 16 else "=%sB" 30 | data_bin = struct.pack(data_fmt % len(data_list), *data_list) 31 | 32 | # write data 33 | try: 34 | with open(filename, 'wb') as f: 35 | f.write(data_bin) 36 | except IOError as err: 37 | print(err) 38 | exit(-1) -------------------------------------------------------------------------------- /src/sim_functions.h: -------------------------------------------------------------------------------- 1 | // sim_functions.h - Johan Smet - BSD-3-Clause (see LICENSE) 2 | // 3 | // simulation functions for basic logic components 4 | 5 | #ifndef LSIM_SIM_FUNCTIONS_H 6 | #define LSIM_SIM_FUNCTIONS_H 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include "sim_types.h" 13 | 14 | 15 | namespace lsim { 16 | 17 | using SimFuncType = unsigned int; 18 | constexpr SimFuncType SIM_FUNCTION_SETUP = 0; 19 | constexpr SimFuncType SIM_FUNCTION_INPUT_CHANGED = 1; 20 | constexpr SimFuncType SIM_FUNCTION_INDEPENDENT = 2; 21 | 22 | using simulation_func_t = std::function; 23 | using sim_component_functions_t = std::array; 24 | 25 | #define SIM_SETUP_FUNC_BEGIN(type) \ 26 | sim->register_sim_function(COMPONENT_##type, \ 27 | SIM_FUNCTION_SETUP, \ 28 | [](Simulator *sim, SimComponent *comp) { 29 | 30 | #define SIM_INPUT_CHANGED_FUNC_BEGIN(type) \ 31 | sim->register_sim_function(COMPONENT_##type, \ 32 | SIM_FUNCTION_INPUT_CHANGED, \ 33 | [](Simulator *sim, SimComponent *comp) { 34 | 35 | #define SIM_INDEPENDENT_FUNC_BEGIN(type) \ 36 | sim->register_sim_function(COMPONENT_##type, \ 37 | SIM_FUNCTION_INDEPENDENT, \ 38 | [](Simulator *sim, SimComponent *comp) { 39 | 40 | #define SIM_FUNC_END }); 41 | 42 | void sim_register_component_functions(Simulator *sim); 43 | 44 | } // namespace lsim 45 | 46 | 47 | #endif // LSIM_SIM_FUNCTIONS_H -------------------------------------------------------------------------------- /src/gui/imgui/imgui_impl_sdl.h: -------------------------------------------------------------------------------- 1 | // dear imgui: Platform Binding for SDL2 2 | // This needs to be used along with a Renderer (e.g. DirectX11, OpenGL3, Vulkan..) 3 | // (Info: SDL2 is a cross-platform general purpose library for handling windows, inputs, graphics context creation, etc.) 4 | 5 | // Implemented features: 6 | // [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. 7 | // [X] Platform: Clipboard support. 8 | // [X] Platform: Keyboard arrays indexed using SDL_SCANCODE_* codes, e.g. ImGui::IsKeyPressed(SDL_SCANCODE_SPACE). 9 | // [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'. 10 | // Missing features: 11 | // [ ] Platform: SDL2 handling of IME under Windows appears to be broken and it explicitly disable the regular Windows IME. You can restore Windows IME by compiling SDL with SDL_DISABLE_WINDOWS_IME. 12 | 13 | // You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. 14 | // If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp. 15 | // https://github.com/ocornut/imgui 16 | 17 | #pragma once 18 | 19 | struct SDL_Window; 20 | typedef union SDL_Event SDL_Event; 21 | 22 | IMGUI_IMPL_API bool ImGui_ImplSDL2_InitForOpenGL(SDL_Window* window, void* sdl_gl_context); 23 | IMGUI_IMPL_API bool ImGui_ImplSDL2_InitForVulkan(SDL_Window* window); 24 | IMGUI_IMPL_API void ImGui_ImplSDL2_Shutdown(); 25 | IMGUI_IMPL_API void ImGui_ImplSDL2_NewFrame(SDL_Window* window); 26 | IMGUI_IMPL_API bool ImGui_ImplSDL2_ProcessEvent(const SDL_Event* event); 27 | -------------------------------------------------------------------------------- /src/std_helper.h: -------------------------------------------------------------------------------- 1 | // std_helper.h - Johan Smet - BSD-3-Clause (see LICENSE) 2 | // 3 | // some quality-of-life improvements when working with the C++ STL 4 | 5 | #ifndef LSIM_STD_HELPER_H 6 | #define LSIM_STD_HELPER_H 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace lsim { 17 | 18 | // repeating std:: gets tedious and unreadable quickly in some case 19 | using std::begin; 20 | using std::end; 21 | using std::move; 22 | using std::string; 23 | using std::unique_ptr; 24 | 25 | // functions to streamline removing elements from a container 26 | template 27 | void remove(std::vector& container, const T& value) { 28 | container.erase(std::remove(begin(container), end(container), value), end(container)); 29 | } 30 | 31 | template 32 | void remove_owner(std::vector>& container, const T* value) { 33 | container.erase(std::remove_if(begin(container), end(container), [value](const auto& el) {return el.get() == value; }), 34 | end(container)); 35 | } 36 | 37 | template 38 | void remove_value(std::unordered_map& map, const V& value) { 39 | for (auto iter = begin(map); iter != end(map);) { 40 | if (iter->second == value) { 41 | iter = map.erase(iter); 42 | } 43 | else { 44 | ++iter; 45 | } 46 | } 47 | } 48 | 49 | template 50 | void remove_if(std::vector& container, Predicate condition) { 51 | container.erase(std::remove_if(begin(container), end(container), condition), end(container)); 52 | } 53 | 54 | 55 | } // namespace lsim 56 | 57 | #endif // LSIM_STD_HELPER_H 58 | 59 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | 8 | { 9 | "name": "(gdb) Launch Unit Test", 10 | "type": "cppdbg", 11 | "request": "launch", 12 | "program": "${workspaceFolder}/build/test_runner", 13 | "args": [], 14 | "stopAtEntry": false, 15 | "cwd": "${workspaceFolder}", 16 | "environment": [], 17 | "externalConsole": false, 18 | "MIMode": "gdb", 19 | "setupCommands": [ 20 | { 21 | "description": "Enable pretty-printing for gdb", 22 | "text": "-enable-pretty-printing", 23 | "ignoreFailures": true 24 | } 25 | ] 26 | }, 27 | { 28 | "name": "(gdb) Launch lsim_gui", 29 | "type": "cppdbg", 30 | "request": "launch", 31 | "program": "${workspaceFolder}/build/lsim_gui", 32 | "args": ["examples/adder.lsim"], 33 | "stopAtEntry": false, 34 | "cwd": "${workspaceFolder}", 35 | "environment": [], 36 | "externalConsole": false, 37 | "MIMode": "gdb", 38 | "setupCommands": [ 39 | { 40 | "description": "Enable pretty-printing for gdb", 41 | "text": "-enable-pretty-printing", 42 | "ignoreFailures": true 43 | } 44 | ] 45 | } 46 | ] 47 | } -------------------------------------------------------------------------------- /src/gui/ui_panel_library.cpp: -------------------------------------------------------------------------------- 1 | // ui_panel_library.cpp - Johan Smet - BSD-3-Clause (see LICENSE) 2 | 3 | #include "imgui_ex.h" 4 | 5 | #include "lsim_context.h" 6 | #include "ui_context.h" 7 | #include "ui_popup_files.h" 8 | 9 | namespace lsim { 10 | 11 | namespace gui { 12 | 13 | void ui_panel_library(UIContext* ui_context) { 14 | 15 | // popups 16 | ui_file_selector_define(); 17 | ui_filename_entry_define(); 18 | 19 | if (ImGui::Button("New")) { 20 | ui_context->simulation_stop(); 21 | ui_context->circuit_library_load(""); 22 | } 23 | ImGui::SameLine(); 24 | if (ImGui::Button("Load")) { 25 | ui_context->simulation_stop(); 26 | ui_file_selector_open(ui_context->lsim_context(), [ui_context](const auto& selection) { 27 | ui_context->circuit_library_load(selection); 28 | }); 29 | } 30 | ImGui::SameLine(); 31 | #ifndef __EMSCRIPTEN__ 32 | if (ImGui::Button("Save")) { 33 | if (!ui_context->library_filename().empty()) { 34 | ui_context->circuit_library_save(ui_context->library_filename()); 35 | } else { 36 | ui_filename_entry_open([ui_context](const auto& filename) { 37 | ui_context->circuit_library_save(filename); 38 | }); 39 | } 40 | } 41 | #endif // __EMSCRIPTEN__ 42 | ImGui::SameLine(); 43 | if (ImGui::Button("Add Library")) { 44 | ui_file_selector_open(ui_context->lsim_context(), [ui_context](const std::string& selection) { 45 | auto name_begin = selection.find_last_of('/') + 1; 46 | auto name_end = selection.find_last_of('.'); 47 | auto name = selection.substr(name_begin, name_end - name_begin); 48 | auto context = ui_context->lsim_context(); 49 | context->load_reference_library(name.c_str(), context->relative_file_path(selection).c_str()); 50 | context->user_library()->add_reference(name.c_str()); 51 | }); 52 | } 53 | } 54 | 55 | } // namespace lsim::gui 56 | 57 | } // namespace lsim 58 | -------------------------------------------------------------------------------- /src/lsim_context.h: -------------------------------------------------------------------------------- 1 | // lsim_context.h - Johan Smet - BSD-3-Clause (see LICENSE) 2 | 3 | #ifndef LSIM_LSIM_CONTEXT_H 4 | #define LSIM_LSIM_CONTEXT_H 5 | 6 | #include "simulator.h" 7 | #include "model_circuit_library.h" 8 | 9 | namespace lsim { 10 | 11 | class ModelCircuit; 12 | 13 | class LSimContext { 14 | public: 15 | LSimContext() : m_user_library("user") { 16 | sim_register_component_functions(&m_sim); 17 | }; 18 | 19 | Simulator *sim() {return &m_sim;} 20 | ModelCircuitLibrary *user_library() {return &m_user_library;} 21 | 22 | // circuits 23 | ModelCircuit *create_user_circuit(const char *name) { 24 | return m_user_library.create_circuit(name, this); 25 | } 26 | ModelCircuit *find_circuit(const char *name, ModelCircuitLibrary *fallback_lib = nullptr); 27 | 28 | // reference libraries 29 | void load_reference_library(const char *name, const char *filename); 30 | void clear_reference_libraries(); 31 | ModelCircuitLibrary *library_by_name(const char *name); 32 | 33 | // folders 34 | void add_folder(const char *name, const char *path); 35 | size_t num_folders() const {return m_folders.size();} 36 | std::string folder_name(size_t folder_idx); 37 | std::string folder_path(size_t folder_idx); 38 | std::string full_file_path(const std::string &file); 39 | std::string relative_file_path(const std::string &file); 40 | 41 | private: 42 | using library_lut_t = std::unordered_map; 43 | using folder_lut_t = std::unordered_map; 44 | 45 | private: 46 | Simulator m_sim; 47 | ModelCircuitLibrary m_user_library; 48 | library_lut_t m_reference_libraries; 49 | 50 | std::vector m_folders; 51 | folder_lut_t m_folder_lut; 52 | }; 53 | 54 | } // namespace lsim 55 | 56 | #endif // LSIM_LSIM_CONTEXT_H -------------------------------------------------------------------------------- /src/tools/rom_led_decimal_8b.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # rom_led_decimal_8b.py: ROM that replaces the logic to display a 8-bit value on 4 7-segment LED modules 4 | # - input : [0-7] = the number to display 5 | # [8-9] = index of the digit to display (0 = least significant digit) 6 | # - output : 8 bits (the segments to light up) 7 | # 8 | # +-a-+ 9 | # f b 10 | # +-g-+ 11 | # e c 12 | # +-d-+ dp 13 | # 14 | 15 | import struct 16 | 17 | digit_data = [ 18 | # .gfedcba 19 | 0b00111111, # 0 20 | 0b00000110, # 1 21 | 0b01011011, # 2 22 | 0b01001111, # 3 23 | 0b01100110, # 4 24 | 0b01101101, # 5 25 | 0b01111101, # 6 26 | 0b00000111, # 7 27 | 0b01111111, # 8 28 | 0b01101111, # 9 29 | 0b01110111, # A 30 | 0b01111100, # B 31 | 0b00111001, # C 32 | 0b01011110, # D 33 | 0b01111001, # E 34 | 0b01110001 # F 35 | # .gfedcba 36 | ] 37 | 38 | def write_binary(filename, data_list, word_size): 39 | assert word_size in [8,16,32], "word_size should be 8/16/32" 40 | 41 | # pack data 42 | data_fmt = "=%sl" if word_size == 32 else "=%sh" if word_size == 16 else "=%sB" 43 | data_bin = struct.pack(data_fmt % len(data_list), *data_list) 44 | 45 | # write data 46 | try: 47 | with open(filename, 'wb') as f: 48 | f.write(data_bin) 49 | except IOError as err: 50 | print(err) 51 | exit(-1) 52 | 53 | def assemble_address(index, number): 54 | return ((index & 0b11) << 8) | (number & 0b11111111) 55 | 56 | def main(): 57 | rom_data = [0 for i in range(2**10)] 58 | 59 | for number in range(256): 60 | for idx in range(3): 61 | digit = int(number / 10**idx) % 10 62 | rom_data[assemble_address(idx, number)] = digit_data[digit] 63 | 64 | write_binary("led_decimal_8b.bin", rom_data, 8) 65 | 66 | if __name__ == "__main__": 67 | main() -------------------------------------------------------------------------------- /src/gui/ui_context.h: -------------------------------------------------------------------------------- 1 | // ui_context.h - Johan Smet - BSD-3-Clause (see LICENSE) 2 | 3 | #ifndef LSIM_GUI_UI_CONTEXT_H 4 | #define LSIM_GUI_UI_CONTEXT_H 5 | 6 | #include "std_helper.h" 7 | #include "circuit_editor.h" 8 | #include 9 | #include 10 | 11 | namespace lsim { 12 | 13 | class LSimContext; 14 | class ModelCircuit; 15 | class ModelComponent; 16 | class SimCircuit; 17 | 18 | namespace gui { 19 | 20 | class UIContext { 21 | public: 22 | UIContext(LSimContext* lsim_context); 23 | 24 | // accessors 25 | LSimContext* lsim_context() const { return m_lsim_context; } 26 | std::string library_filename() const { return m_lib_filename; } 27 | int selected_circuit_idx() const { return m_selected_circuit_idx; } 28 | CircuitEditor* circuit_editor() const { return m_circuit_editor.get(); } 29 | SimCircuit* sim_circuit() const { return m_sim_circuit.get(); } 30 | 31 | // library management 32 | void circuit_library_load(const std::string& filename); 33 | void circuit_library_save(const std::string& filename); 34 | void circuit_library_close(); 35 | 36 | // circuit management 37 | void change_active_circuit(ModelCircuit* circuit); 38 | void change_selected_circuit_idx(int delta) { m_selected_circuit_idx += delta; } 39 | 40 | // simulation control 41 | void simulation_start(); 42 | void simulation_stop(); 43 | 44 | // sub-circuit views 45 | void create_sub_circuit_view(SimCircuit* sim_circuit, ModelComponent *model_comp); 46 | void foreach_sub_circuit_view(const std::function &callback); 47 | 48 | private: 49 | LSimContext* m_lsim_context; 50 | std::string m_lib_filename = ""; 51 | int m_selected_circuit_idx = 0; 52 | unique_ptr m_circuit_editor = nullptr; 53 | unique_ptr m_sim_circuit = nullptr; 54 | std::list> m_sub_circuit_views; 55 | }; 56 | 57 | } // namespace lsim::gui 58 | 59 | } // namespace lsim 60 | 61 | #endif // LSIM_GUI_UI_CONTEXT_H 62 | -------------------------------------------------------------------------------- /src/tools/speedtest/speedtest_main.cpp: -------------------------------------------------------------------------------- 1 | // speedtest_main.cpp - Johan Smet - BSD-3-Clause (see LICENSE) 2 | // 3 | // Performance analysis test harness 4 | 5 | #include "lsim_context.h" 6 | #include "serialize.h" 7 | #include 8 | #include 9 | #include 10 | 11 | namespace { 12 | 13 | const char *TEST_CIRCUIT = "examples/test_led.lsim"; 14 | const int CYCLE_COUNT = 5000000; 15 | 16 | using namespace std::chrono; 17 | steady_clock::time_point chrono_ref; 18 | 19 | inline void chrono_reset() { 20 | chrono_ref = steady_clock::now(); 21 | } 22 | 23 | inline double chrono_report() { 24 | duration span = duration_cast>(steady_clock::now() - chrono_ref); 25 | return span.count(); 26 | } 27 | 28 | 29 | } // unnamed namespace 30 | 31 | int main() { 32 | lsim::LSimContext lsim_context; 33 | 34 | std::printf("--- setting up LSim\n"); 35 | chrono_reset(); 36 | lsim_context.add_folder("examples", "../examples"); 37 | if (!lsim::deserialize_library(&lsim_context, lsim_context.user_library(), lsim_context.full_file_path(TEST_CIRCUIT).c_str())) { 38 | std::printf("!!! unable to load circuit (%s)\n", TEST_CIRCUIT); 39 | return -1; 40 | } 41 | std::printf("+++ done (%f seconds)\n", chrono_report()); 42 | 43 | std::printf("--- instantiating circuit\n"); 44 | chrono_reset(); 45 | auto circuit_desc = lsim_context.user_library()->circuit_by_name("decimal display"); 46 | auto circuit = circuit_desc->instantiate(lsim_context.sim()); 47 | std::printf("+++ done (%f seconds)\n", chrono_report()); 48 | 49 | std::printf("*** initializing simulator\n"); 50 | lsim_context.sim()->init(); 51 | 52 | std::printf("+++ running simulation for %d cycles\n", CYCLE_COUNT); 53 | chrono_reset(); 54 | for (int i = 0; i < CYCLE_COUNT; ++i) { 55 | lsim_context.sim()->step(); 56 | } 57 | double duration = chrono_report(); 58 | std::printf("+++ done (%f seconds): %.2f Hz (%.2f kHz)\n", duration, CYCLE_COUNT / duration, CYCLE_COUNT / (duration * 1000)); 59 | 60 | return 0; 61 | } 62 | -------------------------------------------------------------------------------- /src/gui/ui_panel_circuit.cpp: -------------------------------------------------------------------------------- 1 | // ui_panel_circuit.cpp - Johan Smet - BSD-3-Clause (see LICENSE) 2 | 3 | #include "imgui_ex.h" 4 | 5 | #include "circuit_editor.h" 6 | #include "lsim_context.h" 7 | #include "ui_context.h" 8 | 9 | namespace lsim { 10 | 11 | namespace gui { 12 | 13 | void ui_panel_circuit(UIContext* ui_context) { 14 | 15 | auto lib = ui_context->lsim_context()->user_library(); 16 | const auto selected_circuit_idx = ui_context->selected_circuit_idx(); 17 | 18 | // move up 19 | if (ImGui::Button("Up")) { 20 | if (selected_circuit_idx < lib->num_circuits() && selected_circuit_idx > 0) { 21 | lib->swap_circuits(selected_circuit_idx, selected_circuit_idx - 1); 22 | ui_context->change_selected_circuit_idx(-1); 23 | } 24 | } 25 | 26 | // move down 27 | ImGui::SameLine(); 28 | if (ImGui::Button("Down")) { 29 | if (selected_circuit_idx < lib->num_circuits() - 1) { 30 | lib->swap_circuits(selected_circuit_idx, selected_circuit_idx + 1); 31 | ui_context->change_selected_circuit_idx(+1); 32 | } 33 | } 34 | 35 | // add a new circuit to the library 36 | ImGui::SameLine(); 37 | if (ImGui::Button("Add")) { 38 | auto new_name = std::string("circuit#") + std::to_string(lib->num_circuits() + 1); 39 | auto circuit = lib->create_circuit(new_name.c_str(), ui_context->lsim_context()); 40 | if (lib->main_circuit() == nullptr) { 41 | lib->change_main_circuit(circuit->name().c_str()); 42 | } 43 | ui_context->change_active_circuit(circuit); 44 | } 45 | 46 | // remove circuit from library if allowed 47 | if (lib->num_circuits() > 1 && lib->main_circuit() != lib->circuit_by_idx(selected_circuit_idx)) { 48 | ImGui::SameLine(); 49 | if (ImGui::Button("Delete")) { 50 | lib->delete_circuit(lib->circuit_by_idx(selected_circuit_idx)); 51 | ui_context->change_active_circuit(lib->main_circuit()); 52 | } 53 | } 54 | 55 | // list of circuits in the user library 56 | ImGui::Separator(); 57 | 58 | for (size_t i = 0; i < lib->num_circuits(); ++i) { 59 | auto circuit = lib->circuit_by_idx(i); 60 | if (ImGui::Selectable(circuit->name().c_str(), selected_circuit_idx == i)) { 61 | ui_context->change_active_circuit(circuit); 62 | } 63 | } 64 | 65 | ImGui::Separator(); 66 | } 67 | 68 | } // namespace lsim::gui 69 | 70 | } // namespace lsim 71 | 72 | -------------------------------------------------------------------------------- /docs/todo.md: -------------------------------------------------------------------------------- 1 | # LSim: Todo 2 | 3 | ## Simulation Engine 4 | 5 | - [ ] Each basic component always takes exactly 1 simulation step to procedure its output. To provide a more realistic simulation a little entropy should be introduced here. For example, the datasheet of a 74LS00 chip (quad 2-input nand gate) specifies it typically takes 9ns to toggle from LOW to HIGH but it could take up to 15ns worst case. 6 | - [ ] The simulation engine spends a lot of time process each fixed simulation step even if it should be able to tell nothings going to change in the next batch of cycles (barring user input). A more event-driven approach might allow us to bring the simulation cycle length down to the micro-, or even the nanosecond in real-time. 7 | - [ ] The simulation engine is single-threaded. Extending it to use multiple threads is non-trivial and probably unnecessary for the typical circuit being simulated. But for larger circuits, e.g. with large ROMs or RAM subcircuits, it might prove to be helpful. 8 | 9 | ## User interface 10 | 11 | Although it's usable the GUI still needs a lot of TLC to be enjoyable to use. Up until now, the GUI has not been the focus of attention itself just a necessary evil to be able to build circuits. As such a user needs to be familiar, and willing to be put up, with its many quirks and shortcomings. 12 | 13 | - [ ] Allow wires to be selected and moved along with regular components 14 | - [ ] Auto-route wires when moving components 15 | - [ ] Specify the speed of in Hertz; not in steps per display frame as it is now 16 | - [ ] Streamline saving/loading of circuit libraries 17 | - [ ] Indication that circuit was changed but not saved yet (+ popup when closing LSim) 18 | 19 | ## Python bindings 20 | 21 | - [ ] Cleanup and complete exposed Python API 22 | - [ ] Add parameter information to all exposed functions 23 | - [ ] Figure out how to package/install the bindings so they can be used with explicitly setting the PYTHONPATH environment variable 24 | 25 | ## WebUI (LSim wasm) 26 | 27 | - [ ] Add persistent storage option; or at least warn user that saving isn't persistent 28 | - [ ] Import/export circuits 29 | 30 | ## New features 31 | 32 | - [ ] A logic analyzer view or export of data for [GTKWave](http://gtkwave.sourceforge.net/) or [sigrok](https://sigrok.org/) would be a great help in debugging more complex circuits. 33 | 34 | -------------------------------------------------------------------------------- /src/gui/component_icon.cpp: -------------------------------------------------------------------------------- 1 | // component_icon.h - Johan Smet - BSD-3-Clause (see LICENSE) 2 | 3 | #include "component_icon.h" 4 | 5 | #include "imgui_ex.h" 6 | #define NANOSVG_IMPLEMENTATION 7 | #include "nanosvg.h" 8 | 9 | namespace lsim { 10 | 11 | namespace gui { 12 | 13 | ComponentIcon::icon_lut_t ComponentIcon::m_icon_cache; 14 | 15 | ComponentIcon::ComponentIcon(const char* data, size_t len) { 16 | std::vector dummy(data, data + len); 17 | auto img = nsvgParse(dummy.data(), "px", 96); 18 | 19 | Point offset(img->width / 2.0f, img->height / 2.0f); 20 | m_size = { img->width, img->height }; 21 | 22 | for (auto shape = img->shapes; shape != nullptr; shape = shape->next) { 23 | for (auto path = shape->paths; path != nullptr; path = path->next) { 24 | for (int i = 0; i < path->npts - 1; i += 3) { 25 | float* p = &path->pts[i * 2]; 26 | m_curves.push_back({ 27 | Point(p[0], p[1]) - offset, Point(p[2], p[3]) - offset, 28 | Point(p[4], p[5]) - offset, Point(p[6], p[7]) - offset 29 | }); 30 | } 31 | } 32 | } 33 | } 34 | 35 | void ComponentIcon::draw(Transform transform, Point draw_size, ImDrawList* draw_list, size_t line_width, uint32_t color) const { 36 | 37 | Point scale_xy = draw_size / m_size; 38 | float scale = std::min(scale_xy.x, scale_xy.y); 39 | 40 | for (const auto& curve : m_curves) { 41 | draw_list->AddBezierCurve( 42 | transform.apply(curve[0] * scale), 43 | transform.apply(curve[1] * scale), 44 | transform.apply(curve[2] * scale), 45 | transform.apply(curve[3] * scale), 46 | color, 47 | static_cast(line_width)); 48 | } 49 | } 50 | 51 | void ComponentIcon::draw(Point origin, Point draw_size, ImDrawList* draw_list, size_t line_width, uint32_t color) const { 52 | Transform transform; 53 | transform.translate(origin); 54 | draw(transform, draw_size, draw_list, line_width, color); 55 | } 56 | 57 | ComponentIcon* ComponentIcon::cache(uint32_t id, const char* data, size_t len) { 58 | m_icon_cache[id] = std::make_unique(data, len); 59 | return cached(id); 60 | } 61 | 62 | ComponentIcon* ComponentIcon::cached(uint32_t id) { 63 | auto result = m_icon_cache.find(id); 64 | if (result != m_icon_cache.end()) { 65 | return result->second.get(); 66 | } 67 | return nullptr; 68 | } 69 | 70 | } // namespace lsim::gui 71 | 72 | } // namespace lsim 73 | -------------------------------------------------------------------------------- /src/sim_circuit.h: -------------------------------------------------------------------------------- 1 | // sim_circuit.h - Johan Smet - BSD-3-Clause (see LICENSE) 2 | // 3 | // instantiation of a circuit description 4 | 5 | #ifndef LSIM_SIM_CIRCUIT_H 6 | #define LSIM_SIM_CIRCUIT_H 7 | 8 | #include "model_circuit.h" 9 | 10 | namespace lsim { 11 | 12 | class SimComponent; 13 | 14 | class SimCircuit { 15 | public: 16 | SimCircuit(Simulator *sim, ModelCircuit *circuit_desc); 17 | ModelCircuit *description() const {return m_circuit_desc;} 18 | 19 | // instantiation 20 | SimComponent *add_component(ModelComponent *comp); 21 | node_t add_wire(ModelWire *wire); 22 | void connect_pins(pin_id_t pin_a, pin_id_t pin_b); 23 | SimComponent *component_by_id(uint32_t comp_id); 24 | 25 | // name 26 | void build_name(uint32_t comp_id); 27 | const char *name() const {return m_name.c_str();} 28 | 29 | // read/write 30 | Value read_pin(pin_id_t pin_id); 31 | uint8_t read_nibble(uint32_t comp_id); 32 | uint8_t read_nibble(const pin_id_container_t &pins); 33 | uint8_t read_byte(uint32_t comp_id); 34 | uint8_t read_byte(const pin_id_container_t &pins); 35 | uint64_t read_pins(const pin_id_container_t &pins); 36 | 37 | void write_pin(pin_id_t pin_id, Value value); 38 | void write_output_pins(uint32_t comp_id, value_container_t values); 39 | void write_output_pins(uint32_t comp_id, uint64_t data); 40 | void write_output_pins(uint32_t comp_id, Value value); 41 | 42 | void write_nibble(const pin_id_container_t &pins, uint8_t data); 43 | void write_byte(const pin_id_container_t &pins, uint8_t data); 44 | void write_pins(const pin_id_container_t &pins, const value_container_t &values); 45 | void write_pins(const pin_id_container_t &pins, uint64_t data); 46 | 47 | node_t pin_node(pin_id_t pin_id); 48 | bool node_dirty(node_t node_id); 49 | 50 | // get value written to by a specific pin 51 | Value pin_output(pin_id_t pin_id); 52 | Value user_value(pin_id_t pin_id); 53 | 54 | private: 55 | pin_t pin_from_pin_id(pin_id_t pin_id); 56 | 57 | private: 58 | using sim_component_lut_t = std::unordered_map; 59 | 60 | private: 61 | ModelCircuit * m_circuit_desc; 62 | Simulator * m_sim; 63 | sim_component_lut_t m_components; 64 | std::string m_name; 65 | }; 66 | 67 | 68 | } 69 | 70 | 71 | #endif // LSIM_SIM_CIRCUIT_H -------------------------------------------------------------------------------- /src/gui/shell_minimal.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | LSim - A digital logic simulator 7 | 29 | 30 | 31 | 32 | 62 | {{{ SCRIPT }}} 63 | 64 | 65 | -------------------------------------------------------------------------------- /src/model_circuit_library.h: -------------------------------------------------------------------------------- 1 | // model_circuit_library.h - Johan Smet - BSD-3-Clause (see LICENSE) 2 | // 3 | // a collection of circuits 4 | 5 | #ifndef LSIM_MODEL_CIRCUIT_LIBRARY_H 6 | #define LSIM_MODEL_CIRCUIT_LIBRARY_H 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include "model_circuit.h" 13 | 14 | namespace lsim { 15 | 16 | class LSimContext; 17 | 18 | class ModelCircuitLibrary { 19 | public: 20 | using uptr_t = std::unique_ptr; 21 | using reference_container_t = std::vector; 22 | 23 | public: 24 | ModelCircuitLibrary(const char *name, const char *path= ""); 25 | ModelCircuitLibrary(const ModelCircuitLibrary &other) = delete; 26 | 27 | const char *name() const {return m_name.c_str();} 28 | const char *path() const {return m_path.c_str();} 29 | 30 | const char *main_circuit_name() const {return m_main_circuit.c_str();} 31 | ModelCircuit *main_circuit() const {return circuit_by_name(m_main_circuit.c_str());} 32 | void change_main_circuit(const char *main) {m_main_circuit = main;} 33 | 34 | // circuit management 35 | ModelCircuit *create_circuit(const char *name, LSimContext *context); 36 | void delete_circuit(ModelCircuit *circuit); 37 | void rename_circuit(ModelCircuit *circuit, const char *name); 38 | void swap_circuits(size_t idx_a, size_t idx_b); 39 | size_t num_circuits() const {return m_circuits.size();} 40 | ModelCircuit *circuit_by_idx(size_t idx) const; 41 | ModelCircuit *circuit_by_name(const char *name) const; 42 | uint32_t circuit_idx(ModelCircuit *circuit) const; 43 | void clear_circuits(); 44 | 45 | // references 46 | void add_reference(const char *name); 47 | void remove_reference(const char *name); 48 | void clear_references(); 49 | const reference_container_t &references() const {return m_references;} 50 | 51 | private: 52 | using circuit_container_t = std::vector; 53 | using circuit_map_t = std::unordered_map; 54 | 55 | private: 56 | std::string m_name; 57 | std::string m_path; 58 | 59 | circuit_container_t m_circuits; 60 | circuit_map_t m_circuit_lut; 61 | std::string m_main_circuit; 62 | 63 | reference_container_t m_references; 64 | }; 65 | 66 | } // namespace lsim 67 | 68 | #endif // LSIM_MODEL_CIRCUIT_LIBRARY_H 69 | -------------------------------------------------------------------------------- /src/sim_types.h: -------------------------------------------------------------------------------- 1 | // sim_types.h - Johan Smet - BSD-3-Clause (see LICENSE) 2 | // 3 | // basic types for the simulator 4 | 5 | #ifndef LSIM_SIM_TYPES_H 6 | #define LSIM_SIM_TYPES_H 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | namespace lsim { 13 | 14 | using node_t = uint32_t; 15 | using pin_t = uint32_t; 16 | using timestamp_t = uint64_t; 17 | 18 | enum Value { 19 | VALUE_FALSE = 0, 20 | VALUE_TRUE = 1, 21 | VALUE_UNDEFINED = 2, 22 | VALUE_ERROR = 3, 23 | }; 24 | 25 | using node_container_t = std::vector; 26 | using pin_container_t = std::vector; 27 | using value_container_t = std::vector; 28 | 29 | const pin_t PIN_UNDEFINED = static_cast(-1); 30 | const node_t NODE_INVALID = static_cast(-1); 31 | 32 | // pin-ids are used in the circuit description 33 | using pin_id_t = uint64_t; 34 | using pin_id_container_t = std::vector; 35 | 36 | // component types - seems okay for now for these to be all listed here, not really expecting much extra types 37 | using ComponentType = uint32_t; 38 | const ComponentType COMPONENT_CONNECTOR_IN = 0x0001; 39 | const ComponentType COMPONENT_CONNECTOR_OUT = 0x0002; 40 | const ComponentType COMPONENT_CONSTANT = 0x0003; 41 | const ComponentType COMPONENT_PULL_RESISTOR = 0x0004; 42 | const ComponentType COMPONENT_BUFFER = 0x0011; 43 | const ComponentType COMPONENT_TRISTATE_BUFFER = 0x0012; 44 | const ComponentType COMPONENT_AND_GATE = 0x0013; 45 | const ComponentType COMPONENT_OR_GATE = 0x0014; 46 | const ComponentType COMPONENT_NOT_GATE = 0x0015; 47 | const ComponentType COMPONENT_NAND_GATE = 0x0016; 48 | const ComponentType COMPONENT_NOR_GATE = 0x0017; 49 | const ComponentType COMPONENT_XOR_GATE = 0x0018; 50 | const ComponentType COMPONENT_XNOR_GATE = 0x0019; 51 | const ComponentType COMPONENT_VIA = 0x0020; 52 | const ComponentType COMPONENT_OSCILLATOR = 0x0021; 53 | const ComponentType COMPONENT_7_SEGMENT_LED = 0x0101; 54 | const ComponentType COMPONENT_SUB_CIRCUIT = 0x0301; 55 | const ComponentType COMPONENT_TEXT = 0x0401; 56 | const ComponentType COMPONENT_MAX_TYPE_ID = COMPONENT_TEXT; 57 | 58 | // component extra data types 59 | struct ExtraDataOscillator { 60 | timestamp_t m_next_change; 61 | int64_t m_duration[2]; 62 | }; 63 | 64 | struct ExtraData7SegmentLED { 65 | size_t m_num_samples; 66 | uint32_t m_samples[8]; 67 | }; 68 | 69 | // forward declarations 70 | class ModelComponent; 71 | class ModelCircuit; 72 | 73 | class SimComponent; 74 | class Simulator; 75 | 76 | } // namespace lsim 77 | 78 | #endif // LSIM_SIM_TYPES_H 79 | -------------------------------------------------------------------------------- /src/gui/imgui/imgui_impl_opengl3.h: -------------------------------------------------------------------------------- 1 | // dear imgui: Renderer for modern OpenGL with shaders / programmatic pipeline 2 | // - Desktop GL: 3.x 4.x 3 | // - Embedded GL: ES 2.0 (WebGL 1.0), ES 3.0 (WebGL 2.0) 4 | // This needs to be used along with a Platform Binding (e.g. GLFW, SDL, Win32, custom..) 5 | 6 | // Implemented features: 7 | // [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID in imgui.cpp. 8 | 9 | // You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. 10 | // If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp. 11 | // https://github.com/ocornut/imgui 12 | 13 | // About Desktop OpenGL function loaders: 14 | // Modern desktop OpenGL doesn't have a standard portable header file to load OpenGL function pointers. 15 | // Helper libraries are often used for this purpose! Here we are supporting a few common ones (gl3w, glew, glad). 16 | // You may use another loader/header of your choice (glext, glLoadGen, etc.), or chose to manually implement your own. 17 | 18 | // About GLSL version: 19 | // The 'glsl_version' initialization parameter should be NULL (default) or a "#version XXX" string. 20 | // On computer platform the GLSL version default to "#version 130". On OpenGL ES 3 platform it defaults to "#version 300 es" 21 | // Only override if your GL version doesn't handle this GLSL version. See GLSL version table at the top of imgui_impl_opengl3.cpp. 22 | 23 | #pragma once 24 | 25 | // Specific OpenGL versions 26 | //#define IMGUI_IMPL_OPENGL_ES2 // Auto-detected on Emscripten 27 | //#define IMGUI_IMPL_OPENGL_ES3 // Auto-detected on iOS/Android 28 | 29 | // Set default OpenGL3 loader to be gl3w 30 | #if !defined(IMGUI_IMPL_OPENGL_LOADER_GL3W) \ 31 | && !defined(IMGUI_IMPL_OPENGL_LOADER_GLEW) \ 32 | && !defined(IMGUI_IMPL_OPENGL_LOADER_GLAD) \ 33 | && !defined(IMGUI_IMPL_OPENGL_LOADER_CUSTOM) 34 | #define IMGUI_IMPL_OPENGL_LOADER_GL3W 35 | #endif 36 | 37 | IMGUI_IMPL_API bool ImGui_ImplOpenGL3_Init(const char* glsl_version = NULL); 38 | IMGUI_IMPL_API void ImGui_ImplOpenGL3_Shutdown(); 39 | IMGUI_IMPL_API void ImGui_ImplOpenGL3_NewFrame(); 40 | IMGUI_IMPL_API void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data); 41 | 42 | // Called by Init/NewFrame/Shutdown 43 | IMGUI_IMPL_API bool ImGui_ImplOpenGL3_CreateFontsTexture(); 44 | IMGUI_IMPL_API void ImGui_ImplOpenGL3_DestroyFontsTexture(); 45 | IMGUI_IMPL_API bool ImGui_ImplOpenGL3_CreateDeviceObjects(); 46 | IMGUI_IMPL_API void ImGui_ImplOpenGL3_DestroyDeviceObjects(); 47 | -------------------------------------------------------------------------------- /src/gui/component_widget.h: -------------------------------------------------------------------------------- 1 | // component_widget.h - Johan Smet - BSD-3-Clause (see LICENSE) 2 | 3 | #ifndef LSIM_GUI_COMPONENT_WIDGET_H 4 | #define LSIM_GUI_COMPONENT_WIDGET_H 5 | 6 | #include 7 | #include "std_helper.h" 8 | 9 | #include "algebra.h" 10 | #include "sim_types.h" 11 | 12 | namespace lsim { 13 | 14 | namespace gui { 15 | 16 | class ComponentIcon; 17 | class CircuitEditor; 18 | 19 | class ComponentWidget { 20 | public: 21 | using endpoint_map_t = std::unordered_map; 22 | using uptr_t = std::unique_ptr; 23 | using draw_func_t = std::function; 24 | 25 | public: 26 | ComponentWidget(ModelComponent* comp); 27 | 28 | // access properties 29 | ModelComponent* component_model() const { return m_component; } 30 | bool has_border() const { return m_border; } 31 | bool has_tooltip() const { return !m_tooltip.empty(); } 32 | const char* tooltip() const { return m_tooltip.c_str(); } 33 | const ComponentIcon* icon() const { return m_icon; } 34 | const Transform& to_circuit() const { return m_to_circuit; } 35 | const Point& aabb_min() const { return m_aabb_min; } 36 | const Point& aabb_max() const { return m_aabb_max; } 37 | Point aabb_size() const { return m_aabb_max - m_aabb_min; } 38 | 39 | // change properties 40 | void show_border(bool show) { m_border = show; } 41 | void change_tooltip(const char* tooltip); 42 | void change_icon(const ComponentIcon* icon); 43 | void change_size(float width, float height); 44 | void build_transform(); 45 | 46 | // custom draw function 47 | void set_draw_callback(draw_func_t func); 48 | bool has_draw_callback() const { return m_draw_callback != nullptr; } 49 | void run_draw_callback(CircuitEditor* circuit, Transform transform); 50 | 51 | // endpoints 52 | void add_endpoint(pin_id_t pin, Point location); 53 | void add_pin_line(pin_id_t pin_start, size_t pin_count, float size, Point origin, Point inc); 54 | void add_pin_line(pin_id_t pin_start, size_t pin_count, Point origin, Point delta); 55 | const endpoint_map_t& endpoints() const { return m_endpoints; } 56 | 57 | void dematerialize(); 58 | 59 | private: 60 | void recompute_aabb(); 61 | 62 | private: 63 | ModelComponent* m_component; 64 | bool m_border; 65 | string m_tooltip; 66 | Transform m_to_circuit; 67 | Point m_half_size; 68 | Point m_aabb_min; 69 | Point m_aabb_max; 70 | const ComponentIcon* m_icon; 71 | draw_func_t m_draw_callback; 72 | 73 | endpoint_map_t m_endpoints; 74 | }; 75 | 76 | } // namespace lsim::gui 77 | 78 | } // namespace lsim 79 | 80 | #endif // LSIM_GUI_COMPONENT_WIDGET_H 81 | -------------------------------------------------------------------------------- /src/algebra.h: -------------------------------------------------------------------------------- 1 | // algebra.h - Johan Smet - BSD-3-Clause (see LICENSE) 2 | // 3 | // basic algebra classes - only implements things used in lsim 4 | 5 | #ifndef LSIM_GUI_ALGEBRA_H 6 | #define LSIM_GUI_ALGEBRA_H 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | namespace lsim { 13 | 14 | inline bool float_equal(float a, float b, float epsilon = FLT_EPSILON) { 15 | return fabs(a - b) <= fmaxf(fabs(a), fabs(b))* epsilon; 16 | } 17 | 18 | class Point { 19 | public: 20 | // constructors 21 | Point() = default; 22 | Point(float x, float y); 23 | Point(const Point &other) = default; 24 | Point(const std::array a); 25 | 26 | // operators 27 | Point operator+(const Point &p) const; 28 | Point operator-(const Point &p) const; 29 | Point operator-() const; 30 | Point operator*(float scale) const; 31 | Point operator*(int scale) const; 32 | Point operator*(const Point &p) const; 33 | Point operator/(const Point &p) const; 34 | 35 | public: 36 | float x = 0.0f; 37 | float y = 0.0f; 38 | }; 39 | 40 | float distance_squared(const Point &p1, const Point &p2); 41 | 42 | inline bool operator==(const Point &p1, const Point &p2) { 43 | return float_equal(p1.x, p2.x) && float_equal(p1.y, p2.y); 44 | } 45 | 46 | inline bool operator!=(const Point &p1, const Point &p2) { 47 | return !float_equal(p1.x, p2.x) || !float_equal(p1.y, p2.y); 48 | } 49 | 50 | inline bool points_colinear(const Point &p0, const Point &p1, const Point &p2) { 51 | // return true if all three points are on the same line 52 | return fabs(((p1.x - p0.x) * (p2.y - p0.y)) - ((p2.x - p0.x) * (p1.y - p0.y))) < 0.001f; 53 | } 54 | 55 | inline bool between(float a, float b, float v) { 56 | // return true if v is between a and b 57 | if (a < b) { 58 | return (v >= a) && (v <= b); 59 | } else { 60 | return (v >= b) && (v <= a); 61 | } 62 | } 63 | 64 | inline bool point_on_line_segment(const Point &s0, const Point &s1, const Point &p) { 65 | // return true if p is on the line-segment formed by s0 and s1 66 | return points_colinear(s0, s1, p) && 67 | between(s0.x, s1.x, p.x) && 68 | between(s0.y, s1.y, p.y); 69 | } 70 | 71 | class Transform { 72 | public: 73 | Transform() = default; 74 | Transform(const Transform &other) = default; 75 | 76 | float el(int c, int r) const {return m_el[c][r];} 77 | 78 | void reset(); 79 | void rotate(float degrees); 80 | void translate(const Point &delta); 81 | 82 | Point apply(const Point &p) const; 83 | Point apply_to_vector(const Point &v) const; 84 | 85 | private: 86 | float m_el[3][2] = {{1.0f, 0.0f}, {0.0f, 1.0f}, {0.0f, 0.0f}}; // 3 columns, 2 rows 87 | }; 88 | 89 | } // namespace lsim 90 | 91 | #endif // LSIM_GUI_ALGEBRA_H 92 | -------------------------------------------------------------------------------- /src/model_circuit_library.cpp: -------------------------------------------------------------------------------- 1 | // model_circuit_library.cpp - Johan Smet - BSD-3-Clause (see LICENSE) 2 | // 3 | // a collection of circuits 4 | 5 | #include "model_circuit_library.h" 6 | #include "std_helper.h" 7 | #include 8 | 9 | namespace lsim { 10 | 11 | ModelCircuitLibrary::ModelCircuitLibrary(const char *name, const char *path) : 12 | m_name(name), 13 | m_path(path) { 14 | } 15 | 16 | ModelCircuit *ModelCircuitLibrary::create_circuit(const char *name, LSimContext *context) { 17 | assert(name); 18 | 19 | m_circuits.push_back(std::make_unique(name, context, this)); 20 | auto circuit = m_circuits.back().get(); 21 | m_circuit_lut[name] = circuit; 22 | 23 | return circuit; 24 | } 25 | 26 | void ModelCircuitLibrary::delete_circuit(ModelCircuit *circuit) { 27 | assert (std::find_if(m_circuits.begin(), m_circuits.end(), [=](auto &o) {return o.get() == circuit;}) != m_circuits.end()); 28 | remove_value(m_circuit_lut, circuit); 29 | remove_owner(m_circuits, circuit); 30 | } 31 | 32 | void ModelCircuitLibrary::rename_circuit(ModelCircuit *circuit, const char *name) { 33 | assert(circuit); 34 | assert(name); 35 | 36 | // FIXME: check for duplicates 37 | remove_value(m_circuit_lut, circuit); 38 | m_circuit_lut[name] = circuit; 39 | 40 | circuit->change_name(name); 41 | } 42 | 43 | void ModelCircuitLibrary::swap_circuits(size_t idx_a, size_t idx_b) { 44 | assert(idx_a < m_circuits.size()); 45 | assert(idx_b < m_circuits.size()); 46 | 47 | std::swap(m_circuits[idx_a], m_circuits[idx_b]); 48 | } 49 | 50 | ModelCircuit *ModelCircuitLibrary::circuit_by_idx(size_t idx) const { 51 | assert(idx < num_circuits()); 52 | return m_circuits[idx].get(); 53 | } 54 | 55 | ModelCircuit *ModelCircuitLibrary::circuit_by_name(const char *name) const { 56 | assert(name); 57 | 58 | auto res = m_circuit_lut.find(name); 59 | if (res != m_circuit_lut.end()) { 60 | return res->second; 61 | } 62 | 63 | return nullptr; 64 | } 65 | 66 | uint32_t ModelCircuitLibrary::circuit_idx(ModelCircuit *circuit) const { 67 | for (auto i = 0u; i < m_circuits.size(); ++i) { 68 | if (m_circuits[i].get() == circuit) { 69 | return i; 70 | } 71 | } 72 | 73 | return m_circuits.size(); 74 | } 75 | 76 | void ModelCircuitLibrary::clear_circuits() { 77 | m_circuits.clear(); 78 | m_circuit_lut.clear(); 79 | } 80 | 81 | void ModelCircuitLibrary::add_reference(const char *name) { 82 | if (std::find(m_references.begin(), m_references.end(), name) != m_references.end()) { 83 | return; 84 | } 85 | 86 | m_references.push_back(name); 87 | } 88 | 89 | void ModelCircuitLibrary::remove_reference(const char *name) { 90 | remove(m_references, string(name)); 91 | } 92 | 93 | void ModelCircuitLibrary::clear_references() { 94 | m_references.clear(); 95 | } 96 | 97 | } // namespace lsim -------------------------------------------------------------------------------- /src/sim_component.h: -------------------------------------------------------------------------------- 1 | // sim_component.h - Johan Smet - BSD-3-Clause (see LICENSE) 2 | 3 | #ifndef LSIM_SIM_COMPONENT_H 4 | #define LSIM_SIM_COMPONENT_H 5 | 6 | #include "sim_types.h" 7 | #include "sim_circuit.h" 8 | #include 9 | 10 | namespace lsim { 11 | 12 | class Simulator; 13 | 14 | class SimComponent { 15 | public: 16 | using uptr_t = std::unique_ptr; 17 | public: 18 | SimComponent(Simulator* sim, ModelComponent* comp, uint32_t id); 19 | ModelComponent* description() const { return m_comp_desc; } 20 | uint32_t id() const { return m_id; } 21 | 22 | void apply_initial_values(); 23 | 24 | // pins 25 | pin_t pin_by_index(uint32_t index) const; 26 | uint32_t input_pin_index(uint32_t index) const { return index; } 27 | uint32_t output_pin_index(uint32_t index) const { return m_output_start + index; } 28 | uint32_t control_pin_index(uint32_t index) const { return m_control_start + index; } 29 | const pin_container_t& pins() const { return m_pins; } 30 | pin_container_t input_pins() const; 31 | pin_container_t output_pins() const; 32 | pin_container_t control_pins() const; 33 | size_t num_inputs() const { return m_output_start; } 34 | size_t num_outputs() const { return m_control_start - m_output_start; } 35 | size_t num_controls() const { return m_pins.size() - m_control_start; } 36 | 37 | // read/write_pin: read/write the value of the node the specified pin connects to 38 | Value read_pin(uint32_t index) const; 39 | void write_pin(uint32_t index, Value value); 40 | 41 | // read/write_checked: convenience functions that make it easy to check if 42 | // all the read nodes (since last bad-read reset) had a valid boolean 43 | // value (not undefined or error) 44 | bool read_pin_checked(uint32_t index); 45 | void write_pin_checked(uint32_t index, bool value); 46 | void reset_bad_read_check() { m_read_bad = false; } 47 | 48 | // user_values: input from outside the circuit 49 | void enable_user_values(); 50 | Value user_value(uint32_t index) const; 51 | void set_user_value(uint32_t index, Value value); 52 | bool user_values_enabled() const { return !m_user_values.empty(); } 53 | 54 | // nested circuits 55 | void set_nested_instance(std::unique_ptr instance); 56 | SimCircuit* nested_instance() const { return m_nested_circuit.get(); } 57 | 58 | // extra-data: component specific data structure 59 | void set_extra_data_size(size_t size) { m_extra_data.resize(size); }; 60 | uint8_t* extra_data() { return m_extra_data.data(); } 61 | 62 | private: 63 | Simulator* m_sim; 64 | ModelComponent* m_comp_desc; 65 | uint32_t m_id; 66 | 67 | pin_container_t m_pins; 68 | value_container_t m_user_values; 69 | std::vector m_extra_data; 70 | 71 | uint32_t m_output_start; 72 | uint32_t m_control_start; 73 | bool m_read_bad; 74 | 75 | std::unique_ptr m_nested_circuit; 76 | }; 77 | 78 | } // namespace lsim 79 | 80 | #endif // LSIM_SIM_COMPONENT_H 81 | -------------------------------------------------------------------------------- /src/algebra.cpp: -------------------------------------------------------------------------------- 1 | // algebra.cpp - Johan Smet - BSD-3-Clause (see LICENSE) 2 | // 3 | // basic algebra classes - only implements things used in lsim 4 | 5 | #include "algebra.h" 6 | #include 7 | #include 8 | 9 | namespace { 10 | 11 | constexpr const float PI = 3.14159265358979323846f; 12 | 13 | inline float deg2rad(float deg) { 14 | return (deg * PI) / 180.0f; 15 | } 16 | 17 | } // unnamed namespace 18 | 19 | namespace lsim { 20 | 21 | /////////////////////////////////////////////////////////////////////////////// 22 | // 23 | // Point 24 | // 25 | 26 | Point::Point(float x, float y) : x(x), y(y) { 27 | } 28 | 29 | Point::Point(const std::array a) : x(a[0]), y(a[1]) { 30 | 31 | } 32 | 33 | Point Point::operator+ (const Point& p) const { 34 | return Point(x + p.x, y + p.y); 35 | } 36 | 37 | Point Point::operator- (const Point& p) const { 38 | return Point(x - p.x, y - p.y); 39 | } 40 | 41 | Point Point::operator- () const { 42 | return Point(-x, -y); 43 | } 44 | 45 | Point Point::operator*(float scale) const { 46 | return Point(x * scale, y * scale); 47 | } 48 | 49 | Point Point::operator*(int scale) const { 50 | return Point(x * scale, y * scale); 51 | } 52 | 53 | Point Point::operator*(const Point& p) const { 54 | return Point(x * p.x, y * p.y); 55 | } 56 | 57 | Point Point::operator/(const Point& p) const { 58 | return Point(x / p.x, y / p.y); 59 | } 60 | 61 | float distance_squared(const Point& p1, const Point& p2) { 62 | return ((p2.x - p1.x) * (p2.x - p1.x)) + 63 | ((p2.y - p1.y) * (p2.y - p1.y)); 64 | } 65 | 66 | /////////////////////////////////////////////////////////////////////////////// 67 | // 68 | // Transform 69 | // 70 | 71 | void Transform::reset() { 72 | m_el[0][0] = 1.0f; m_el[1][0] = 0.0f; m_el[2][0] = 0.0f; 73 | m_el[0][1] = 0.0f; m_el[1][1] = 1.0f; m_el[2][1] = 0.0f; 74 | } 75 | 76 | void Transform::rotate(float degrees) { 77 | const float rad = deg2rad(degrees); 78 | const float sa = sinf(rad); 79 | const float ca = cosf(rad); 80 | const Transform cur(*this); 81 | 82 | m_el[0][0] = ca * cur.m_el[0][0] - sa * cur.m_el[0][1]; 83 | m_el[1][0] = ca * cur.m_el[1][0] - sa * cur.m_el[1][1]; 84 | m_el[2][0] = ca * cur.m_el[2][0] - sa * cur.m_el[2][1]; 85 | 86 | m_el[0][1] = sa * cur.m_el[0][0] + ca * cur.m_el[0][1]; 87 | m_el[1][1] = sa * cur.m_el[1][0] + ca * cur.m_el[1][1]; 88 | m_el[2][1] = sa * cur.m_el[2][0] + ca * cur.m_el[2][1]; 89 | } 90 | 91 | void Transform::translate(const Point& delta) { 92 | m_el[2][0] += delta.x; 93 | m_el[2][1] += delta.y; 94 | } 95 | 96 | Point Transform::apply(const Point& p) const { 97 | return Point(m_el[0][0] * p.x + m_el[1][0] * p.y + m_el[2][0], 98 | m_el[0][1] * p.x + m_el[1][1] * p.y + m_el[2][1]); 99 | } 100 | 101 | Point Transform::apply_to_vector(const Point& v) const { 102 | return Point(m_el[0][0] * v.x + m_el[1][0] * v.y, 103 | m_el[0][1] * v.x + m_el[1][1] * v.y); 104 | } 105 | 106 | } // namespace lsim -------------------------------------------------------------------------------- /src/gui/imgui_ex.cpp: -------------------------------------------------------------------------------- 1 | // imgui_ex.cpp - Johan Smet - BSD-3-Clause (see LICENSE) 2 | // 3 | // QOL extensions for imgui 4 | 5 | #include "imgui_ex.h" 6 | #include "imgui/imgui_internal.h" 7 | 8 | namespace { 9 | 10 | int transform_start_index = 0; 11 | 12 | inline ImVec2 aligned_position(ImVec2 at, const char *text, ImGuiEx::TextAlignHor align_hor, ImGuiEx::TextAlignVer align_ver) { 13 | 14 | if (align_hor == ImGuiEx::TAH_LEFT && align_ver == ImGuiEx::TAV_TOP) { 15 | return at; 16 | } 17 | 18 | auto pos = at; 19 | auto size = ImGui::CalcTextSize(text); 20 | 21 | if (align_hor == ImGuiEx::TAH_RIGHT) { 22 | pos.x -= size.x; 23 | } else if (align_hor == ImGuiEx::TAH_CENTER) { 24 | pos.x -= size.x / 2.0f; 25 | } 26 | 27 | if (align_ver == ImGuiEx::TAV_BOTTOM) { 28 | pos.y -= size.y; 29 | } else if (align_ver == ImGuiEx::TAV_CENTER) { 30 | pos.y -= size.y / 2.0f; 31 | } 32 | return pos; 33 | } 34 | 35 | } // unnamed namespace 36 | 37 | namespace ImGuiEx { 38 | 39 | void Text(ImVec2 at, const char *text, TextAlignHor align_hor, TextAlignVer align_ver) { 40 | auto pos = aligned_position(at, text, align_hor, align_ver); 41 | ImGui::SetCursorScreenPos(ImVec2(pos.x, pos.y)); 42 | ImGui::Text("%s", text); 43 | } 44 | 45 | void TextNoClip(ImVec2 at, const char *text, ImU32 col, TextAlignHor align_hor, TextAlignVer align_ver) { 46 | // reimplementation of ImDrawList::AddText which does not do clipping 47 | // particullary to be used with TransformStart and TransformEnd 48 | 49 | auto pos = aligned_position(at, text, align_hor, align_ver); 50 | 51 | if ((col & IM_COL32_A_MASK) == 0) { 52 | return; 53 | } 54 | 55 | auto text_begin = text; 56 | auto text_end = text_begin + strlen(text_begin); 57 | if (text_begin == text_end) { 58 | return; 59 | } 60 | 61 | // Pull default font/size from the shared ImDrawListSharedData instance 62 | auto draw_list = ImGui::GetWindowDrawList(); 63 | const auto font = draw_list->_Data->Font; 64 | const auto font_size = draw_list->_Data->FontSize; 65 | const auto no_clipping = ImVec4(-8192.0f, -8192.0f, +8192.0f, +8192.0f); 66 | 67 | font->RenderText(draw_list, font_size, pos, col, no_clipping, text_begin, text_end); 68 | } 69 | 70 | void RectFilled(ImVec2 p1, ImVec2 p2, ImU32 col) { 71 | auto min = p1; 72 | auto max = p2; 73 | 74 | if (max.x < min.x) { 75 | std::swap(min.x, max.x); 76 | } 77 | 78 | if (max.y < min.y) { 79 | std::swap(min.y, max.y); 80 | } 81 | 82 | ImGui::GetWindowDrawList()->AddRectFilled(min, max, col); 83 | } 84 | 85 | // TransformStart & TransformEnd based on ideas from https://gist.github.com/carasuca/e72aacadcf6cf8139de46f97158f790f 86 | void TransformStart() { 87 | transform_start_index = ImGui::GetWindowDrawList()->VtxBuffer.Size; 88 | } 89 | 90 | void TransformEnd(lsim::Transform transform) { 91 | auto &vtx_buf = ImGui::GetWindowDrawList()->VtxBuffer; 92 | 93 | for (auto i = transform_start_index; i < vtx_buf.Size; ++i) { 94 | vtx_buf[i].pos = transform.apply(vtx_buf[i].pos); 95 | } 96 | } 97 | 98 | } // namespace ImGuiEx 99 | -------------------------------------------------------------------------------- /src/gui/component_widget.cpp: -------------------------------------------------------------------------------- 1 | // component_widget.cpp - Johan Smet - BSD-3-Clause (see LICENSE) 2 | 3 | #include "component_widget.h" 4 | 5 | #include "configuration.h" 6 | #include "model_component.h" 7 | 8 | namespace lsim { 9 | 10 | namespace gui { 11 | 12 | ComponentWidget::ComponentWidget(ModelComponent* component_model) : 13 | m_component(component_model), 14 | m_border(true), 15 | m_tooltip(""), 16 | m_half_size(0.0f, 0.0f), 17 | m_icon(nullptr), 18 | m_draw_callback(nullptr) { 19 | build_transform(); 20 | } 21 | 22 | void ComponentWidget::change_tooltip(const char* tooltip) { 23 | m_tooltip = tooltip; 24 | } 25 | 26 | void ComponentWidget::change_icon(const ComponentIcon* icon) { 27 | m_icon = icon; 28 | } 29 | 30 | void ComponentWidget::build_transform() { 31 | m_to_circuit.reset(); 32 | m_to_circuit.rotate(static_cast(m_component->angle())); 33 | m_to_circuit.translate(m_component->position()); 34 | recompute_aabb(); 35 | } 36 | 37 | void ComponentWidget::change_size(float width, float height) { 38 | m_half_size = { width / 2.0f, height / 2.0f }; 39 | recompute_aabb(); 40 | } 41 | 42 | void ComponentWidget::recompute_aabb() { 43 | m_aabb_min = m_to_circuit.apply({ -m_half_size.x, -m_half_size.y }); 44 | m_aabb_max = m_to_circuit.apply({ m_half_size.x, m_half_size.y }); 45 | if (m_aabb_max.x < m_aabb_min.x) { 46 | std::swap(m_aabb_min.x, m_aabb_max.x); 47 | } 48 | 49 | if (m_aabb_max.y < m_aabb_min.y) { 50 | std::swap(m_aabb_min.y, m_aabb_max.y); 51 | } 52 | } 53 | 54 | void ComponentWidget::set_draw_callback(ComponentWidget::draw_func_t func) { 55 | m_draw_callback = move(func); 56 | } 57 | 58 | void ComponentWidget::run_draw_callback(CircuitEditor* circuit, Transform transform) { 59 | if (m_draw_callback) { 60 | m_draw_callback(circuit, this, transform); 61 | } 62 | } 63 | 64 | void ComponentWidget::add_endpoint(pin_id_t pin, Point location) { 65 | m_endpoints[pin] = location; 66 | } 67 | 68 | void ComponentWidget::add_pin_line(pin_id_t pin_start, size_t pin_count, float size, Point origin, Point inc) { 69 | if (pin_count == 0) { 70 | return; 71 | } 72 | 73 | auto odd = pin_count % 2; 74 | float half = (pin_count - odd) * 0.5f; 75 | float segment_len = size / (2.0f * (half + 1)); 76 | segment_len = roundf(segment_len / GRID_SIZE) * GRID_SIZE; 77 | auto segment_delta = inc * segment_len; 78 | auto pin = pin_start; 79 | 80 | // top half 81 | Point pos = origin - (segment_delta * half); 82 | 83 | for (size_t idx = 0; idx < half; ++idx) { 84 | add_endpoint(pin++, pos); 85 | pos = pos + segment_delta; 86 | } 87 | 88 | // center 89 | if (odd > 0) { 90 | add_endpoint(pin++, origin); 91 | } 92 | 93 | // bottom half 94 | pos = origin + segment_delta; 95 | 96 | for (size_t idx = 0; idx < half; ++idx) { 97 | add_endpoint(pin++, pos); 98 | pos = pos + segment_delta; 99 | } 100 | } 101 | 102 | void ComponentWidget::add_pin_line(pin_id_t pin_start, size_t pin_count, Point origin, Point delta) { 103 | if (pin_count == 0) { 104 | return; 105 | } 106 | 107 | auto pos = origin; 108 | for (size_t i = 0; i < pin_count; ++i) { 109 | add_endpoint(pin_start + i, pos); 110 | pos = pos + delta; 111 | } 112 | } 113 | 114 | void ComponentWidget::dematerialize() { 115 | m_endpoints.clear(); 116 | } 117 | 118 | } // namespace lsim::gui 119 | 120 | } // namespace lsim 121 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LSim 2 | LSim (or Logicial SIMulator) is a tool to simulate digital logic circuits. It's a hobby/toy project I started to help me learn about digital logic design. 3 | 4 | Please note: if you're not into custom-built experimental logic simulators this might not be the place for you. For a motivation (or more accurately: a justification) why I build LSim please read this [post](http://justcode.be/projects/lsim/). 5 | 6 | ![lsim_small](docs/lsim_small.png) 7 | 8 | ## Features 9 | 10 | - simulation logic circuits at the gate level 11 | - a somewhat usable GUI to build and visualize digital circuits 12 | - construct basic circuits a reuse them as buildings blocks for more complex circuits 13 | - Python-bindings to enable easy exhaustive simulation of your circuit 14 | - the Python-bindings also allow procedural generation of circuits (e.g. roms) 15 | - can simulate basic digital circuits (e.g. flipflops and counters) and more complex circuits. As an example, LSim includes a simplistic 8-bit computer that can do some basic operations (e.g. compute the Fibonacci sequence). 16 | - Runs on multiple platforms: Linux, Windows, MacOS X and using WebAssembly in most modern browsers. You can try a demo of the WebAssembly version over [here](http://justcode.be/lsim/). 17 | 18 | ## Structure 19 | 20 | The core of the project is a library called `lsim` . The library contains the logic simulator and the corresponding logic gates and supporting components. The library separates construction and modification of the digital circuit from the simulation of said circuit in a two-phased approach. 21 | 22 | In the first phase, a model of the circuit is constructed using basic components, nested circuits, and wires. The second phase instantiates a flattened, immutable representation of said model that is easier to simulate. 23 | 24 | The library also contains the necessary support to store and load circuit models and preliminary Python bindings that allows easy scripting of circuit tests and procedural generation of large, repetitive circuits. 25 | 26 | The GUI application, `lsim_gui`, wraps the library and provides an user interface to build circuits and visualize the circuit being simulated. 27 | 28 | ## More information 29 | 30 | - [Building](docs/building.md) LSim from source 31 | - [Todo](docs/todo.md). LSim is a work-in-progress. This lists some major point left to tackle. 32 | - How to use the [Python bindings](docs/using_python_bindings.md). 33 | - A bit of documentation on how to use the [LSim GUI](docs/using_lsim_gui.md). 34 | 35 | ## License 36 | 37 | This project is licensed under the 3-clause BSD License. See [LICENSE](LICENSE) for the full text. 38 | 39 | ## **Acknowledgments** 40 | 41 | - The GUI is built with the awesome [Dear ImGui](https://github.com/ocornut/imgui). Building the cross-platform UI would not have been as much fun without it. 42 | - I trust upon [pugixml](https://pugixml.org/) to save me from the horrors of parsing XML files. 43 | - [Catch2](https://github.com/catchorg/Catch2) is the unit-test framework that, hopefully, saves me from making the same mistake twice. 44 | - [pybind11](https://github.com/pybind/pybind11) enabled me to add Python bindings without to much hassle. 45 | - cute_files (part of [cute_headers](https://github.com/RandyGaul/cute_headers)) allows zipping around the filesystem on all supported platforms. 46 | - Logisim (http://www.cburch.com/logisim/) for being part of the inspiration to start this project. 47 | -------------------------------------------------------------------------------- /src/sim_gates.cpp: -------------------------------------------------------------------------------- 1 | // sim_gates.cpp - Johan Smet - BSD-3-Clause (see LICENSE) 2 | // 3 | // basic logic gates 4 | 5 | #include "sim_functions.h" 6 | #include "simulator.h" 7 | 8 | namespace lsim { 9 | 10 | void sim_register_gate_functions(Simulator *sim) { 11 | 12 | SIM_INPUT_CHANGED_FUNC_BEGIN(BUFFER) { 13 | for (auto pin = 0u; pin < comp->num_inputs(); ++pin) { 14 | auto value = comp->read_pin(comp->input_pin_index(pin)); 15 | comp->write_pin(comp->output_pin_index(pin), value); 16 | } 17 | } SIM_FUNC_END 18 | 19 | SIM_INPUT_CHANGED_FUNC_BEGIN(TRISTATE_BUFFER) { 20 | if (comp->read_pin(comp->control_pin_index(0)) != VALUE_TRUE) { 21 | for (auto pin = 0u; pin < comp->num_outputs(); ++pin) { 22 | comp->write_pin(comp->output_pin_index(pin), VALUE_UNDEFINED); 23 | } 24 | } else { 25 | for (auto pin = 0u; pin < comp->num_inputs(); ++pin) { 26 | auto value = comp->read_pin(comp->input_pin_index(pin)); 27 | comp->write_pin(comp->output_pin_index(pin), value); 28 | } 29 | } 30 | } SIM_FUNC_END 31 | 32 | SIM_INPUT_CHANGED_FUNC_BEGIN(AND_GATE) { 33 | comp->reset_bad_read_check(); 34 | 35 | bool output = comp->read_pin_checked(0); 36 | for (auto idx = 1u; idx < comp->num_inputs(); ++idx) { 37 | output &= comp->read_pin_checked(idx); 38 | } 39 | comp->write_pin_checked(comp->output_pin_index(0), output); 40 | } SIM_FUNC_END 41 | 42 | SIM_INPUT_CHANGED_FUNC_BEGIN(OR_GATE) { 43 | comp->reset_bad_read_check(); 44 | 45 | bool output = comp->read_pin_checked(0); 46 | for (auto idx = 1u; idx < comp->num_inputs(); ++idx) { 47 | output |= comp->read_pin_checked(idx); 48 | } 49 | comp->write_pin_checked(comp->output_pin_index(0), output); 50 | } SIM_FUNC_END 51 | 52 | SIM_INPUT_CHANGED_FUNC_BEGIN(NOT_GATE) { 53 | comp->reset_bad_read_check(); 54 | auto input = comp->read_pin_checked(0); 55 | comp->write_pin_checked(1, !input); 56 | } SIM_FUNC_END 57 | 58 | SIM_INPUT_CHANGED_FUNC_BEGIN(NAND_GATE) { 59 | comp->reset_bad_read_check(); 60 | 61 | bool output = comp->read_pin_checked(0); 62 | for (auto idx = 1u; idx < comp->num_inputs(); ++idx) { 63 | output &= comp->read_pin_checked(idx); 64 | } 65 | comp->write_pin_checked(comp->output_pin_index(0), !output); 66 | } SIM_FUNC_END 67 | 68 | SIM_INPUT_CHANGED_FUNC_BEGIN(NOR_GATE) { 69 | comp->reset_bad_read_check(); 70 | 71 | bool output = comp->read_pin_checked(0); 72 | for (auto idx = 1u; idx < comp->num_inputs(); ++idx) { 73 | output |= comp->read_pin_checked(idx); 74 | } 75 | comp->write_pin_checked(comp->output_pin_index(0), !output); 76 | } SIM_FUNC_END 77 | 78 | SIM_INPUT_CHANGED_FUNC_BEGIN(XOR_GATE) { 79 | comp->reset_bad_read_check(); 80 | 81 | auto output = comp->read_pin_checked(0); 82 | output ^= static_cast(comp->read_pin_checked(1)); 83 | comp->write_pin_checked(2, output); 84 | } SIM_FUNC_END 85 | 86 | SIM_INPUT_CHANGED_FUNC_BEGIN(XNOR_GATE) { 87 | comp->reset_bad_read_check(); 88 | 89 | auto output = comp->read_pin_checked(0); 90 | output ^= static_cast(comp->read_pin_checked(1)); 91 | comp->write_pin_checked(2, !output); 92 | } SIM_FUNC_END 93 | } 94 | 95 | 96 | } // namespace lsim -------------------------------------------------------------------------------- /src/bench/bench_counters.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import lsimpy 4 | from bench_utils import * 5 | 6 | def cycle_clock(sim, circuit): 7 | circuit.write_port("Clk", lsimpy.ValueTrue) 8 | sim.run_until_stable(2) 9 | circuit.write_port("Clk", lsimpy.ValueFalse) 10 | sim.run_until_stable(2) 11 | 12 | def test_bin_counter_4b(lsim): 13 | print("*** running BinCounter 4b") 14 | sim = lsim.sim() 15 | 16 | circuit_desc = lsim.user_library().circuit_by_name('BinCounter 4b') 17 | 18 | pins_D = [circuit_desc.port_by_name(f"D[{i:}]") for i in range(0,4)] 19 | pins_Y = [circuit_desc.port_by_name(f"Y[{i:}]") for i in range(0,4)] 20 | 21 | circuit = circuit_desc.instantiate(sim) 22 | sim.init() 23 | 24 | circuit.write_nibble(pins_D, 0) 25 | circuit.write_port("Load", lsimpy.ValueFalse) 26 | circuit.write_port("Clk", lsimpy.ValueFalse) 27 | circuit.write_port("Res", lsimpy.ValueTrue) 28 | circuit.write_port("En", lsimpy.ValueTrue) 29 | sim.run_until_stable(2) 30 | circuit.write_port("Res", lsimpy.ValueFalse) 31 | sim.run_until_stable(2) 32 | CHECK(circuit.read_nibble(pins_Y), 0, "reset") 33 | 34 | for i in range(1, 2**4): 35 | cycle_clock(sim, circuit) 36 | CHECK(circuit.read_nibble(pins_Y), i, "clock cycle") 37 | CHECK(circuit.read_port("RCO"), i == (2**4)-1, "") 38 | 39 | circuit.write_nibble(pins_D, 5) 40 | circuit.write_port("Load", lsimpy.ValueTrue) 41 | sim.run_until_stable(2) 42 | circuit.write_port("Load", lsimpy.ValueFalse) 43 | sim.run_until_stable(2) 44 | CHECK(circuit.read_nibble(pins_Y), 5, "after load") 45 | cycle_clock(sim, circuit) 46 | CHECK(circuit.read_nibble(pins_Y), 6, "increment") 47 | 48 | def test_bin_counter_8b(lsim): 49 | print("*** running BinCounter 8b") 50 | sim = lsim.sim() 51 | 52 | circuit_desc = lsim.user_library().circuit_by_name('BinCounter 8b') 53 | 54 | pins_D = [circuit_desc.port_by_name(f"D[{i:}]") for i in range(0,8)] 55 | pins_Y = [circuit_desc.port_by_name(f"Y[{i:}]") for i in range(0,8)] 56 | 57 | circuit = circuit_desc.instantiate(sim) 58 | sim.init() 59 | 60 | circuit.write_byte(pins_D, 0) 61 | circuit.write_port("Load", lsimpy.ValueFalse) 62 | circuit.write_port("Clk", lsimpy.ValueFalse) 63 | circuit.write_port("Res", lsimpy.ValueTrue) 64 | circuit.write_port("En", lsimpy.ValueTrue) 65 | sim.run_until_stable(2) 66 | circuit.write_port("Res", lsimpy.ValueFalse) 67 | sim.run_until_stable(2) 68 | CHECK(circuit.read_byte(pins_Y), 0, "reset") 69 | 70 | for i in range(1, 2**8): 71 | cycle_clock(sim, circuit) 72 | CHECK(circuit.read_byte(pins_Y), i, "clock cycle") 73 | CHECK(circuit.read_port("RCO"), i == (2**8)-1, "") 74 | 75 | circuit.write_byte(pins_D, 5) 76 | circuit.write_port("Load", lsimpy.ValueTrue) 77 | sim.run_until_stable(2) 78 | circuit.write_port("Load", lsimpy.ValueFalse) 79 | sim.run_until_stable(2) 80 | CHECK(circuit.read_byte(pins_Y), 5, "after load") 81 | cycle_clock(sim, circuit) 82 | CHECK(circuit.read_byte(pins_Y), 6, "increment") 83 | 84 | def main(): 85 | lsim = lsimpy.LSimContext() 86 | lsim.add_folder("examples", "../../examples") 87 | 88 | if (not lsim.load_user_library("examples/cpu_8bit/lib_counter.lsim")): 89 | print("Unable to load circuit\n") 90 | exit(-1) 91 | 92 | test_bin_counter_4b(lsim) 93 | test_bin_counter_8b(lsim) 94 | 95 | print_stats() 96 | 97 | if __name__ == "__main__": 98 | main() -------------------------------------------------------------------------------- /src/gui/ui_context.cpp: -------------------------------------------------------------------------------- 1 | // ui_context.cpp - Johan Smet - BSD-3-Clause (see LICENSE) 2 | 3 | #include "ui_context.h" 4 | 5 | #include "lsim_context.h" 6 | #include "model_circuit.h" 7 | #include "component_widget.h" 8 | 9 | #include "serialize.h" 10 | 11 | namespace lsim { 12 | 13 | namespace gui { 14 | 15 | UIContext::UIContext(LSimContext* lsim_context) : 16 | m_lsim_context(lsim_context) { 17 | } 18 | 19 | void UIContext::circuit_library_load(const std::string& filename) { 20 | if (m_circuit_editor != nullptr) { 21 | circuit_library_close(); 22 | } 23 | 24 | m_lib_filename = filename; 25 | 26 | // fixme: decent error handling would be nice (*cough*) 27 | deserialize_library(m_lsim_context, m_lsim_context->user_library(), filename.c_str()); 28 | 29 | // make sure there is at least one circuit 30 | if (m_lsim_context->user_library()->num_circuits() <= 0) { 31 | m_lsim_context->user_library()->create_circuit("main", m_lsim_context); 32 | m_lsim_context->user_library()->change_main_circuit("main"); 33 | } 34 | 35 | // select main circuit 36 | auto main_circuit = m_lsim_context->user_library()->main_circuit(); 37 | change_active_circuit(main_circuit); 38 | } 39 | 40 | void UIContext::circuit_library_save(const std::string& filename) { 41 | serialize_library(m_lsim_context, m_lsim_context->user_library(), filename.c_str()); 42 | } 43 | 44 | void UIContext::circuit_library_close() { 45 | simulation_stop(); 46 | change_active_circuit(nullptr); 47 | m_lsim_context->user_library()->clear_circuits(); 48 | m_lsim_context->user_library()->clear_references(); 49 | m_lsim_context->clear_reference_libraries(); 50 | } 51 | 52 | void UIContext::change_active_circuit(ModelCircuit* circuit) { 53 | if (m_circuit_editor != nullptr && m_circuit_editor->model_circuit() == circuit) { 54 | return; 55 | } 56 | 57 | if (circuit != nullptr) { 58 | circuit->sync_sub_circuit_components(); 59 | m_circuit_editor = CircuitEditorFactory::create_circuit(circuit); 60 | m_selected_circuit_idx = m_lsim_context->user_library()->circuit_idx(circuit); 61 | } 62 | else { 63 | m_circuit_editor = nullptr; 64 | } 65 | } 66 | 67 | void UIContext::simulation_start() { 68 | auto sim = m_lsim_context->sim(); 69 | 70 | m_sim_circuit = m_circuit_editor->model_circuit()->instantiate(sim); 71 | m_circuit_editor->set_simulation_instance(m_sim_circuit.get()); 72 | sim->init(); 73 | } 74 | 75 | void UIContext::simulation_stop() { 76 | m_sim_circuit = nullptr; 77 | m_circuit_editor->set_simulation_instance(nullptr); 78 | m_sub_circuit_views.clear(); 79 | m_lsim_context->sim()->clear_components(); 80 | } 81 | 82 | void UIContext::create_sub_circuit_view(SimCircuit* sim_circuit, ModelComponent *model_comp) { 83 | auto nested_model = model_comp->nested_circuit(); 84 | auto nested_sim = sim_circuit->component_by_id(model_comp->id())->nested_instance(); 85 | 86 | auto sub_circuit = CircuitEditorFactory::create_circuit(nested_model); 87 | sub_circuit->set_simulation_instance(nested_sim, true); 88 | 89 | m_sub_circuit_views.push_back(move(sub_circuit)); 90 | } 91 | 92 | void UIContext::foreach_sub_circuit_view(const std::function &callback) { 93 | for (auto iter = m_sub_circuit_views.begin(); iter != m_sub_circuit_views.end();) { 94 | auto keep_open = callback(iter->get()); 95 | 96 | if (!keep_open) { 97 | iter = m_sub_circuit_views.erase(iter); 98 | } else { 99 | ++iter; 100 | } 101 | } 102 | } 103 | 104 | } // namespace lsim::gui 105 | 106 | } // namespace lsim 107 | -------------------------------------------------------------------------------- /docs/using_python_bindings.md: -------------------------------------------------------------------------------- 1 | # Using the LSim Python bindings 2 | 3 | LSim provides rudimentary Python 3 bindings to allow you to write test scripts for a circuit or even generate repetitive circuits programmatically. 4 | 5 | 1. Build LSim with Python bindings enabled (`cmake -DPYTHON_BINDINGS=ON`) 6 | 2. Make the resulting module available to Python. You could install it to your system but I just add the build directory to the PYTHONPATH environment variable 7 | 3. Profit. 8 | 9 | ## Testing a circuit 10 | Stepping through a simple test script is probably the easiest way to explain the procedure. There are several test-bench scripts in the `src/bench` directory. We'll look at the test script for an 8-bit comparator circuit. 11 | 12 | First some standard python header stuff. Import the LSim bindings module `lsimpy` and some utility functions from `bench_utils.py`provided in the test-bench directory. 13 | 14 | ```python 15 | #!/usr/bin/env python3 16 | 17 | import lsimpy 18 | from bench_utils import * 19 | ``` 20 | 21 | Now we start the main function by creating a new LSim context. Add a folder so LSim knows where to find any referenced circuit libraries. 22 | 23 | ```python 24 | def main(): 25 | lsim = lsimpy.LSimContext() 26 | lsim.add_folder("examples", "../../examples") 27 | sim = lsim.sim() 28 | ``` 29 | 30 | Next we load the circuit library we want to test: 31 | 32 | ```python 33 | if (not lsim.load_user_library("examples/compare.lsim")): 34 | print("Unable to load circuit\n") 35 | exit(-1) 36 | ``` 37 | 38 | Get the description of the specific circuit we want to test and retrieve pointers to the pins we'll need to write to and read from later on. 39 | 40 | ```python 41 | circuit_desc = lsim.user_library().circuit_by_name("comp_8bit") 42 | 43 | pin_A = [circuit_desc.port_by_name(f"A[{i:}]") for i in range(0,8)] 44 | pin_B = [circuit_desc.port_by_name(f"B[{i:}]") for i in range(0,8)] 45 | pin_LT = circuit_desc.port_by_name("LT") 46 | pin_EQ = circuit_desc.port_by_name("EQ") 47 | pin_GT = circuit_desc.port_by_name("GT") 48 | ``` 49 | Instantiate the circuit in the simulator and start the simulation: 50 | 51 | ```python 52 | circuit = circuit_desc.instantiate(sim) 53 | sim.init() 54 | ``` 55 | 56 | Next we loop through all possible values for the two 8-bit input pins and check if the output pins have to values we expect them to have. `CHECK()` is a function from `bench_utils.py` which reports an error but does not stop the execution of the script. 57 | 58 | ```python 59 | for a in range(0, 2**8): 60 | circuit.write_byte(pin_A, a) 61 | for b in range(0, 2**8): 62 | circuit.write_byte(pin_B, b) 63 | sim.run_until_stable(5) 64 | expected_LT = lsimpy.ValueTrue if a < b else lsimpy.ValueFalse 65 | expected_EQ = lsimpy.ValueTrue if a == b else lsimpy.ValueFalse 66 | expected_GT = lsimpy.ValueTrue if a > b else lsimpy.ValueFalse 67 | CHECK(circuit.read_pin(pin_LT), expected_LT, "{} < {}".format(a, b)) 68 | CHECK(circuit.read_pin(pin_EQ), expected_EQ, "{} == {}".format(a, b)) 69 | CHECK(circuit.read_pin(pin_GT), expected_GT, "{} > {}".format(a, b)) 70 | ``` 71 | 72 | Finally we print some statistics and end the Python script by calling the main function. 73 | 74 | ```python 75 | print_stats() 76 | 77 | if __name__ == "__main__": 78 | main() 79 | ``` 80 | 81 | ## Creating a circuit 82 | 83 | For an example of creating circuits see `src/tools/rom_builder.py`. This scripts takes a binary files and creates a ROM-circuit that can be used in other circuits. 84 | -------------------------------------------------------------------------------- /src/model_component.h: -------------------------------------------------------------------------------- 1 | // model_component.h - Johan Smet - BSD-3-Clause (see LICENSE) 2 | // 3 | // description of a single component 4 | 5 | #ifndef LSIM_MODEL_COMPONENT_H 6 | #define LSIM_MODEL_COMPONENT_H 7 | 8 | #include 9 | #include 10 | 11 | #include "sim_types.h" 12 | #include "model_property.h" 13 | #include "algebra.h" 14 | 15 | namespace lsim { 16 | 17 | using port_lut_t = std::unordered_map; 18 | 19 | inline uint32_t component_id_from_pin_id(pin_id_t pin_id) { 20 | return pin_id >> 32; 21 | } 22 | 23 | inline uint32_t pin_index_from_pin_id(pin_id_t pin_id) { 24 | return pin_id & 0xffffffff; 25 | } 26 | 27 | inline pin_id_t pin_id_assemble(uint32_t component_id, uint32_t pin_index) { 28 | return ((static_cast(component_id)) << 32) | pin_index; 29 | } 30 | 31 | constexpr const auto PIN_ID_INVALID = static_cast(-1); 32 | 33 | class ModelComponent { 34 | public: 35 | using uptr_t = std::unique_ptr; 36 | using property_lut_t = std::unordered_map; 37 | public: 38 | ModelComponent(ModelCircuit *parent, uint32_t id, ComponentType type, uint32_t inputs, uint32_t outputs, uint32_t controls); 39 | ModelComponent(ModelCircuit *parent, uint32_t id, const char *circuit_name, uint32_t inputs, uint32_t outputs); 40 | ModelComponent(const ModelComponent &) = delete; 41 | 42 | uint32_t id() const {return m_id;} 43 | ComponentType type() const {return m_type;} 44 | 45 | // pins 46 | uint32_t num_inputs() const {return m_inputs;} 47 | uint32_t num_outputs() const {return m_outputs;} 48 | uint32_t num_controls() const {return m_controls;} 49 | pin_id_t pin_id(uint32_t index) const; 50 | pin_id_t input_pin_id(uint32_t index) const; 51 | pin_id_t output_pin_id(uint32_t index) const; 52 | pin_id_t control_pin_id(uint32_t index) const; 53 | void change_input_pins(uint32_t new_count); 54 | void change_output_pins(uint32_t new_count); 55 | 56 | pin_id_t port_by_name(const char *name) const; 57 | 58 | // properties 59 | void add_property(Property::uptr_t &&prop); 60 | Property *property(const char *key); 61 | std::string property_value(const char *key, const char *def_value); 62 | int64_t property_value(const char *key, int64_t def_value); 63 | bool property_value(const char *key, bool def_value); 64 | Value property_value(const char *key, Value def_value); 65 | const property_lut_t &properties() const {return m_properties;} 66 | 67 | // position / orientation 68 | const Point &position() const {return m_position;} 69 | int angle() const {return m_angle;} 70 | void set_position(const Point &pos); 71 | void set_angle(int angle); 72 | 73 | // nested circuits 74 | ModelCircuit *nested_circuit() const {return m_nested_circuit;} 75 | bool sync_nested_circuit(class LSimContext *lsim_context); 76 | 77 | // copy & paste 78 | uptr_t copy() const; 79 | void integrate_into_circuit(ModelCircuit *circuit, uint32_t id); 80 | 81 | private: 82 | ModelCircuit *m_circuit; 83 | uint32_t m_id; 84 | ComponentType m_type; 85 | uint32_t m_inputs; 86 | uint32_t m_outputs; 87 | uint32_t m_controls; 88 | 89 | std::string m_nested_name; 90 | ModelCircuit *m_nested_circuit; 91 | port_lut_t m_port_lut; 92 | property_lut_t m_properties; 93 | 94 | Point m_position; 95 | int m_angle; // in degrees 96 | }; 97 | 98 | } // namespace lsim 99 | 100 | #endif // LSIM_MODEL_COMPONENT_H -------------------------------------------------------------------------------- /src/lsim_context.cpp: -------------------------------------------------------------------------------- 1 | // lsim_context.cpp - Johan Smet - BSD-3-Clause (see LICENSE) 2 | 3 | #include "lsim_context.h" 4 | #include "serialize.h" 5 | 6 | #include 7 | 8 | namespace lsim { 9 | 10 | void LSimContext::load_reference_library(const char *name, const char *filename) { 11 | if (m_reference_libraries.find(name) != m_reference_libraries.end()) { 12 | return; 13 | } 14 | 15 | auto lib = std::make_unique(name, filename); 16 | if (!deserialize_library(this, lib.get(), full_file_path(filename).c_str())) { 17 | lib = nullptr; 18 | return; 19 | } 20 | 21 | m_reference_libraries[name] = std::move(lib); 22 | } 23 | 24 | void LSimContext::clear_reference_libraries() { 25 | m_reference_libraries.clear(); 26 | } 27 | 28 | ModelCircuit *LSimContext::find_circuit(const char *name, ModelCircuitLibrary *fallback_lib) { 29 | std::string qualified = name; 30 | auto separator = qualified.find_first_of('.'); 31 | 32 | if (separator == std::string::npos) { 33 | ModelCircuit *result = nullptr; 34 | if (fallback_lib != nullptr) { 35 | result = fallback_lib->circuit_by_name(name); 36 | } 37 | if (result == nullptr) { 38 | result = m_user_library.circuit_by_name(name); 39 | } 40 | return result; 41 | } 42 | 43 | auto lib_name = qualified.substr(0, separator); 44 | auto circuit_name = qualified.substr(separator+1); 45 | 46 | auto found = m_reference_libraries.find(lib_name); 47 | if (found != m_reference_libraries.end()) { 48 | return found->second->circuit_by_name(circuit_name.c_str()); 49 | } 50 | 51 | return nullptr; 52 | } 53 | 54 | ModelCircuitLibrary *LSimContext::library_by_name(const char *name) { 55 | std::string sname = name; 56 | if (sname == "user") { 57 | return &m_user_library; 58 | } 59 | 60 | auto found = m_reference_libraries.find(name); 61 | if (found != m_reference_libraries.end()) { 62 | return found->second.get(); 63 | } 64 | 65 | return nullptr; 66 | } 67 | 68 | void LSimContext::add_folder(const char *name, const char *path) { 69 | m_folders.emplace_back(name); 70 | m_folder_lut[name] = path; 71 | } 72 | 73 | std::string LSimContext::folder_name(size_t folder_idx) { 74 | assert(folder_idx < m_folders.size()); 75 | return m_folders[folder_idx]; 76 | } 77 | 78 | std::string LSimContext::folder_path(size_t folder_idx) { 79 | assert(folder_idx < m_folders.size()); 80 | auto found = m_folder_lut.find(folder_name(folder_idx)); 81 | if (found != m_folder_lut.end()) { 82 | return found->second; 83 | } 84 | return ""; 85 | } 86 | 87 | std::string LSimContext::full_file_path(const std::string &file) { 88 | for (const auto &folder : m_folder_lut) { 89 | const auto &name = folder.first; 90 | const auto &path = folder.second; 91 | 92 | if (file.find_first_of(name) == 0) { 93 | auto result = path + file.substr(name.size()); 94 | return result; 95 | } 96 | } 97 | 98 | return file; 99 | } 100 | 101 | std::string LSimContext::relative_file_path(const std::string &file) { 102 | for (const auto &folder : m_folder_lut) { 103 | const auto &name = folder.first; 104 | const auto &path = folder.second; 105 | 106 | if (file.find_first_of(path) == 0) { 107 | auto result = name + file.substr(path.size()); 108 | return result; 109 | } 110 | } 111 | 112 | return file; 113 | } 114 | 115 | } // namespace lsim -------------------------------------------------------------------------------- /src/sim_component.cpp: -------------------------------------------------------------------------------- 1 | // sim_component.cpp - Johan Smet - BSD-3-Clause (see LICENSE) 2 | 3 | #include "sim_component.h" 4 | #include "sim_circuit.h" 5 | #include "simulator.h" 6 | #include 7 | 8 | namespace lsim { 9 | 10 | SimComponent::SimComponent(Simulator* sim, ModelComponent* comp, uint32_t id) : 11 | m_sim(sim), 12 | m_comp_desc(comp), 13 | m_id(id), 14 | m_read_bad(false), 15 | m_nested_circuit(nullptr) { 16 | 17 | auto num_pins = comp->num_inputs() + comp->num_outputs() + comp->num_controls(); 18 | m_output_start = comp->num_inputs(); 19 | m_control_start = m_output_start + comp->num_outputs(); 20 | for (size_t idx = 0; idx < num_pins; ++idx) { 21 | m_pins.push_back(sim->assign_pin(this, idx < m_output_start || idx >= m_control_start)); 22 | } 23 | } 24 | 25 | void SimComponent::apply_initial_values() { 26 | auto initial_out = m_comp_desc->property_value("initial_output", VALUE_UNDEFINED); 27 | if (initial_out != VALUE_UNDEFINED) { 28 | for (size_t pin = m_output_start; pin < m_control_start; ++pin) { 29 | m_sim->pin_set_initial_value(m_pins[pin], initial_out); 30 | } 31 | } 32 | 33 | if (m_comp_desc->type() == COMPONENT_CONNECTOR_IN && 34 | !m_user_values.empty() && 35 | !m_comp_desc->property_value("tri_state", false)) { 36 | for (size_t pin = m_output_start; pin < m_control_start; ++pin) { 37 | m_user_values[pin] = VALUE_FALSE; 38 | m_sim->pin_set_initial_value(m_pins[pin], initial_out); 39 | } 40 | } 41 | } 42 | 43 | pin_t SimComponent::pin_by_index(uint32_t index) const { 44 | assert(index < m_pins.size()); 45 | return m_pins[index]; 46 | } 47 | 48 | pin_container_t SimComponent::input_pins() const { 49 | return pin_container_t(m_pins.begin(), m_pins.begin() + m_output_start); 50 | } 51 | 52 | pin_container_t SimComponent::output_pins() const { 53 | return pin_container_t(m_pins.begin() + m_output_start, m_pins.begin() + m_control_start); 54 | } 55 | 56 | pin_container_t SimComponent::control_pins() const { 57 | return pin_container_t(m_pins.begin() + m_control_start, m_pins.end()); 58 | } 59 | 60 | Value SimComponent::read_pin(uint32_t index) const { 61 | assert(index < m_pins.size()); 62 | return m_sim->read_pin(m_pins[index]); 63 | } 64 | 65 | void SimComponent::write_pin(uint32_t index, Value value) { 66 | assert(index < m_pins.size()); 67 | // XXX: is the second test really necessary? 68 | if (value == VALUE_UNDEFINED && m_sim->pin_output_value(m_pins[index]) == value) { 69 | return; 70 | } 71 | m_sim->write_pin(m_pins[index], value); 72 | } 73 | 74 | bool SimComponent::read_pin_checked(uint32_t index) { 75 | assert(index < m_pins.size()); 76 | auto value = m_sim->read_pin(m_pins[index]); 77 | m_read_bad |= (value != VALUE_TRUE && value != VALUE_FALSE); 78 | return static_cast(value); 79 | } 80 | 81 | void SimComponent::write_pin_checked(uint32_t index, bool value) { 82 | auto output = m_read_bad ? VALUE_ERROR : static_cast(value); 83 | write_pin(index, static_cast(output)); 84 | } 85 | 86 | void SimComponent::enable_user_values() { 87 | m_user_values.clear(); 88 | m_user_values.resize(m_pins.size(), VALUE_UNDEFINED); 89 | } 90 | 91 | Value SimComponent::user_value(uint32_t index) const { 92 | if (index < m_user_values.size()) { 93 | return m_user_values[index]; 94 | } 95 | return VALUE_UNDEFINED; 96 | } 97 | 98 | void SimComponent::set_user_value(uint32_t index, Value value) { 99 | assert(index < m_pins.size()); 100 | m_user_values[index] = value; 101 | m_sim->activate_independent_simulation_func(this); 102 | } 103 | 104 | void SimComponent::set_nested_instance(std::unique_ptr instance) { 105 | m_nested_circuit = std::move(instance); 106 | } 107 | 108 | } // namespace lsim 109 | -------------------------------------------------------------------------------- /.github/workflows/cmake.yml: -------------------------------------------------------------------------------- 1 | name: LSim build 2 | 3 | on: [push] 4 | 5 | jobs: 6 | test_linux: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | with: 13 | submodules: true 14 | - name: Install dependencies 15 | run: | 16 | sudo add-apt-repository -y "deb http://archive.ubuntu.com/ubuntu `lsb_release -sc` main universe restricted multiverse" 17 | sudo apt-get update -y -qq 18 | sudo apt-get install libsdl2-dev 19 | - name: Create build directory 20 | run: cmake -E make_directory ${{runner.workspace}}/build 21 | - name: Run CMake 22 | shell: bash 23 | working-directory: ${{runner.workspace}}/build 24 | run: cmake $GITHUB_WORKSPACE 25 | - name: Build 26 | shell: bash 27 | working-directory: ${{runner.workspace}}/build 28 | run: cmake --build . 29 | 30 | test_macos: 31 | 32 | runs-on: macos-latest 33 | 34 | steps: 35 | - uses: actions/checkout@v1 36 | with: 37 | submodules: true 38 | - name: Install dependencies 39 | run: | 40 | brew install SDL2 41 | - name: Create build directory 42 | run: cmake -E make_directory ${{runner.workspace}}/build 43 | - name: Run CMake 44 | shell: bash 45 | working-directory: ${{runner.workspace}}/build 46 | run: cmake $GITHUB_WORKSPACE 47 | - name: Build 48 | shell: bash 49 | working-directory: ${{runner.workspace}}/build 50 | run: cmake --build . 51 | 52 | test_windows: 53 | runs-on: windows-latest 54 | 55 | steps: 56 | - uses: actions/checkout@v1 57 | with: 58 | submodules: true 59 | - name: Create build directory 60 | run: cmake -E make_directory ${{runner.workspace}}/build 61 | - uses: actions/checkout@v1 62 | with: 63 | repository: JohanSmet/sdl2_vc_ci 64 | ref: 2.0.10 65 | path: _sdl2 66 | - name: Run CMake 67 | shell: bash 68 | working-directory: ${{runner.workspace}}/build 69 | run: cmake -DSDL2_DIR=../_sdl2 $GITHUB_WORKSPACE 70 | - name: Build 71 | shell: bash 72 | working-directory: ${{runner.workspace}}/build 73 | run: cmake --build . 74 | 75 | test_emscripten: 76 | 77 | runs-on: ubuntu-latest 78 | 79 | steps: 80 | - uses: actions/checkout@v1 81 | with: 82 | submodules: true 83 | - name: Setup Emscripten 84 | uses: mymindstorm/setup-emsdk@v7 85 | with: 86 | version: 2.0.8 87 | actions-cache-folder: 'emsdk-cache' 88 | - name: Run CMake 89 | shell: bash 90 | env: 91 | BUILD_DIR: ${{runner.workspace}}/_build 92 | run: | 93 | mkdir $BUILD_DIR 94 | cd $BUILD_DIR 95 | emcmake cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=Release 96 | - name: Build 97 | shell: bash 98 | env: 99 | BUILD_DIR: ${{runner.workspace}}/_build 100 | run: cmake --build $BUILD_DIR 101 | - name: Prepare artifacts 102 | shell: bash 103 | env: 104 | BUILD_DIR: ${{runner.workspace}}/_build 105 | ARCH_DIR: ${{runner.workspace}}/_arch 106 | run: | 107 | mkdir $ARCH_DIR 108 | cp $BUILD_DIR/lsim.data $ARCH_DIR 109 | cp $BUILD_DIR/lsim.js $ARCH_DIR 110 | cp $BUILD_DIR/lsim.html $ARCH_DIR/index.html 111 | cp $BUILD_DIR/lsim.wasm $ARCH_DIR 112 | - name: Archive build 113 | uses: actions/upload-artifact@v1 114 | with: 115 | name: dist 116 | path: ${{runner.workspace}}/_arch 117 | -------------------------------------------------------------------------------- /src/tools/rom_8bit_program.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # rom_8bit_program.py: generate program ROM for the minimal 8-but computer 4 | 5 | import rom_8bit_common as c 6 | 7 | rom_data = [] 8 | 9 | def emit_literal(raw): 10 | pc = len(rom_data) 11 | rom_data.append(raw & 0xff) 12 | return pc 13 | 14 | def emit_op(op, addr = 0): 15 | raw = (op & 0b00011111) | ((addr & 0b00000111) << 5) 16 | return emit_literal(raw) 17 | 18 | def emit_op_data(op, data): 19 | pc = emit_op(op) 20 | emit_literal(data) 21 | return pc 22 | 23 | def prog_counter(): 24 | emit_op_data(c.OPCODE_LTA, 0) # move 0 into register-A 25 | emit_op_data(c.OPCODE_LTB, 1) # move 1 into register-B 26 | emit_op(c.OPCODE_PRA) # display register-A 27 | l = emit_op(c.OPCODE_ADD) # add register-B to register-A 28 | emit_op(c.OPCODE_PRA) # display register-A 29 | emit_op_data(c.OPCODE_JMP, l) # jump to label-l 30 | emit_op(c.OPCODE_HLT) # halt CPU (never reached but still :-) 31 | 32 | def prog_count_down(): 33 | emit_op_data(c.OPCODE_LTA, 255) # move start value into register-A 34 | emit_op_data(c.OPCODE_LTB, 1) # move 1 into register-B 35 | l = emit_op(c.OPCODE_PRA) # display register-A 36 | emit_op(c.OPCODE_SUB) # subtract register-B from register-A 37 | emit_op_data(c.OPCODE_JMP, l) # jump to label-l 38 | emit_op(c.OPCODE_HLT) # halt CPU (never reached but still :-) 39 | 40 | def prog_count_up_down(): 41 | emit_op_data(c.OPCODE_LTA, 0) # (0+1) move 0 into register-A 42 | emit_op_data(c.OPCODE_LTB, 1) # (2+3) move 1 into register-B 43 | l = emit_op(c.OPCODE_ADD) # (4) add register-B to register-A 44 | emit_op_data(c.OPCODE_JC, 10) # (5+6) jump to second loop if carry flag is set 45 | emit_op(c.OPCODE_PRA) # (7) display register-A 46 | emit_op_data(c.OPCODE_JMP, l) # (8+9) jump to label-l 47 | k = emit_op(c.OPCODE_SUB) # (10) subtract register-B from register-A 48 | emit_op(c.OPCODE_PRA) # (11) display register-A 49 | emit_op_data(c.OPCODE_JZ, l) # (12+13) jump to first loop if zero flag is set 50 | emit_op_data(c.OPCODE_JMP, k) # (14+15) jump to label-k 51 | emit_op(c.OPCODE_HLT) # (16) halt CPU (never reached but still :-) 52 | 53 | def prog_fibonnaci(): 54 | # initialize memory 55 | emit_op_data(c.OPCODE_LTA, 0) # move 0 into register-A 56 | emit_op(c.OPCODE_STA, 1) # store register-A to mem[1] 57 | emit_op_data(c.OPCODE_LTA, 1) # move 1 into register-A 58 | emit_op(c.OPCODE_STA, 2) # store register-A to mem[2] 59 | emit_op_data(c.OPCODE_LTA, 14) # move number of iterations into register-A 60 | emit_op(c.OPCODE_STA, 3) # store register-A to mem[3] 61 | # fibonnaci loop 62 | l = emit_op(c.OPCODE_LDA, 1) # load register-A from mem[1] 63 | emit_op(c.OPCODE_PRA) # display register-A 64 | emit_op(c.OPCODE_LDB, 2) # load register-B from mem[2] 65 | emit_op(c.OPCODE_ADD) # add register-B to register-A 66 | emit_op(c.OPCODE_STB, 1) # store register-B to mem[1] 67 | emit_op(c.OPCODE_STA, 2) # store register-A to mem[2] 68 | emit_op(c.OPCODE_LDA, 3) # load loop-counter into register-A 69 | emit_op_data(c.OPCODE_LTB, 1) # move 1 into register-B 70 | emit_op(c.OPCODE_SUB) # substract register-B from register-A 71 | emit_op(c.OPCODE_STA, 3) # store loop-counter 72 | emit_op_data(c.OPCODE_JZ, 0) # jump to initialization when done 73 | emit_op_data(c.OPCODE_JMP, l) # jump to label-l 74 | emit_op(c.OPCODE_HLT) # halt CPU 75 | 76 | def fill_rom(size): 77 | for i in range(len(rom_data), size): 78 | rom_data.append(0xff) 79 | 80 | def main(): 81 | prog_count_up_down() 82 | fill_rom(256) 83 | prog_fibonnaci() 84 | fill_rom(512) 85 | c.write_binary("prog_8bit.bin", rom_data, 8) 86 | 87 | if __name__ == "__main__": 88 | main() -------------------------------------------------------------------------------- /src/sim_various.cpp: -------------------------------------------------------------------------------- 1 | // sim_various.cpp - Johan Smet - BSD-3-Clause (see LICENSE) 2 | // 3 | // simulation functions for various components 4 | 5 | #include "sim_functions.h" 6 | #include "simulator.h" 7 | #include "model_circuit.h" 8 | 9 | namespace lsim { 10 | 11 | void sim_register_various_functions(Simulator *sim) { 12 | SIM_SETUP_FUNC_BEGIN(CONNECTOR_IN) { 13 | if (!comp->user_values_enabled()) { 14 | return; 15 | } 16 | for (auto pin = 0u; pin < comp->num_outputs(); ++pin) { 17 | auto user_value = comp->user_value(comp->output_pin_index(pin)); 18 | if (user_value != VALUE_UNDEFINED) { 19 | sim->pin_set_initial_value(comp->pin_by_index(pin), user_value); 20 | sim->pin_set_output_value(comp->pin_by_index(pin), user_value); 21 | } 22 | } 23 | 24 | // disable the independent simulation function until the user interacts with the connector 25 | sim->deactivate_independent_simulation_func(comp); 26 | } SIM_FUNC_END; 27 | 28 | SIM_INDEPENDENT_FUNC_BEGIN(CONNECTOR_IN) { 29 | for (auto pin = 0u; pin < comp->num_outputs(); ++pin) { 30 | auto last_value = sim->pin_output_value(comp->pin_by_index(comp->output_pin_index(pin))); 31 | auto user_value = comp->user_value(comp->output_pin_index(pin)); 32 | if (last_value != user_value || user_value != VALUE_UNDEFINED) { 33 | comp->write_pin(pin, user_value); 34 | } 35 | } 36 | 37 | sim->deactivate_independent_simulation_func(comp); 38 | } SIM_FUNC_END; 39 | 40 | SIM_SETUP_FUNC_BEGIN(CONSTANT) { 41 | auto value = comp->description()->property("value")->value_as_lsim_value(); 42 | sim->pin_set_initial_value(comp->pin_by_index(0), value); 43 | } SIM_FUNC_END; 44 | 45 | SIM_SETUP_FUNC_BEGIN(PULL_RESISTOR) { 46 | auto value = comp->description()->property("pull_to")->value_as_lsim_value(); 47 | sim->pin_set_default(comp->pin_by_index(0), value); 48 | sim->pin_set_initial_value(comp->pin_by_index(0), value); 49 | } SIM_FUNC_END; 50 | 51 | SIM_SETUP_FUNC_BEGIN(OSCILLATOR) { 52 | comp->set_extra_data_size(sizeof(ExtraDataOscillator)); 53 | auto *extra = reinterpret_cast(comp->extra_data()); 54 | 55 | auto value = sim->pin_output_value(comp->pin_by_index(0)); 56 | 57 | extra->m_duration[0] = comp->description()->property_value("low_duration", static_cast(1)); 58 | extra->m_duration[1] = comp->description()->property_value("high_duration", static_cast(1)); 59 | extra->m_next_change = sim->current_time() + extra->m_duration[value]; 60 | } SIM_FUNC_END; 61 | 62 | SIM_INDEPENDENT_FUNC_BEGIN(OSCILLATOR) { 63 | auto *extra = reinterpret_cast(comp->extra_data()); 64 | if (sim->current_time() >= extra->m_next_change) { 65 | auto cur_value = sim->pin_output_value(comp->pin_by_index(0)); 66 | auto new_value = cur_value == VALUE_TRUE ? VALUE_FALSE : VALUE_TRUE; 67 | 68 | extra->m_next_change = sim->current_time() + extra->m_duration[new_value]; 69 | comp->write_pin(comp->output_pin_index(0), new_value); 70 | } 71 | } SIM_FUNC_END; 72 | 73 | SIM_SETUP_FUNC_BEGIN(7_SEGMENT_LED) { 74 | comp->set_extra_data_size(sizeof(ExtraData7SegmentLED)); 75 | } SIM_FUNC_END; 76 | 77 | SIM_INDEPENDENT_FUNC_BEGIN(7_SEGMENT_LED) { 78 | auto *extra = reinterpret_cast(comp->extra_data()); 79 | auto led_on = comp->read_pin(comp->control_pin_index(0)); 80 | 81 | if (led_on == VALUE_TRUE) { 82 | for (auto pin_idx = 0u; pin_idx < comp->num_inputs(); ++pin_idx) { 83 | extra->m_samples[pin_idx] += comp->read_pin(comp->input_pin_index(pin_idx)) == VALUE_TRUE ? 1: 0; 84 | } 85 | } 86 | extra->m_num_samples += 1; 87 | } SIM_FUNC_END; 88 | } 89 | 90 | } // namespace lsim -------------------------------------------------------------------------------- /src/model_wire.h: -------------------------------------------------------------------------------- 1 | // model_wire.h - Johan Smet - BSD-3-Clause (see LICENSE) 2 | // 3 | // describe the wires that connect components 4 | 5 | #ifndef LSIM_MODEL_WIRE_H 6 | #define LSIM_MODEL_WIRE_H 7 | 8 | #include 9 | #include 10 | 11 | #include "sim_types.h" 12 | #include "algebra.h" 13 | 14 | namespace lsim { 15 | 16 | class ModelWireJunction { 17 | public: 18 | using uptr_t = std::unique_ptr; 19 | public: 20 | ModelWireJunction(const Point &p, class ModelWireSegment *segment); 21 | 22 | const Point &position() const {return m_position;} 23 | size_t num_segments() const {return m_segments.size();} 24 | class ModelWireSegment *segment(size_t idx) const; 25 | 26 | void add_segment(ModelWireSegment *segment); 27 | void remove_segment(ModelWireSegment *segment); 28 | 29 | void move(const Point& delta); 30 | 31 | private: 32 | Point m_position; 33 | std::vector m_segments; 34 | }; 35 | 36 | class ModelWireSegment { 37 | public: 38 | using uptr_t = std::unique_ptr; 39 | public: 40 | ModelWireSegment(class ModelWire *wire); 41 | 42 | void set_junction(size_t idx, ModelWireJunction *junction); 43 | ModelWireJunction *junction(size_t idx) const; 44 | ModelWire *wire() const {return m_wire;} 45 | 46 | bool point_on_segment(const Point &p); 47 | 48 | void move(const Point& delta); 49 | 50 | private: 51 | class ModelWire *m_wire; 52 | std::array m_ends; 53 | }; 54 | 55 | class ModelWire { 56 | public: 57 | using uptr_t = std::unique_ptr; 58 | using junction_container_t = std::vector; 59 | using segment_container_t = std::vector; 60 | using segment_set_t = std::set; 61 | 62 | public: 63 | ModelWire(uint32_t id); 64 | ModelWire(const ModelWire &) = delete; 65 | uint32_t id() const {return m_id;} 66 | 67 | // pins 68 | void add_pin(pin_id_t pin); 69 | size_t num_pins() const {return m_pins.size();} 70 | pin_id_t pin(size_t index) const; 71 | void remove_component_pins(uint32_t component_id); 72 | void remove_pin(pin_id_t pin); 73 | void clear_pins(); 74 | 75 | // segments & junctions 76 | size_t num_segments() const {return m_segments.size();} 77 | const Point &segment_point(size_t segment_idx, size_t point_idx) const; 78 | ModelWireSegment *segment_by_index(size_t segment_idx); 79 | 80 | size_t num_junctions() const {return m_junctions.size();} 81 | size_t junction_segment_count(size_t idx) const; 82 | const Point &junction_position(size_t idx) const; 83 | 84 | ModelWireJunction *add_junction(const Point &p, ModelWireSegment *segment); 85 | ModelWireSegment *add_segment(const Point &p0, const Point &p1); 86 | ModelWireJunction *create_new_junction(const Point &p, ModelWireSegment *segment); 87 | void add_segments(Point *anchors, size_t num_anchors); 88 | void merge(ModelWire *other); 89 | void split_at_new_junction(const Point &p); 90 | void move(const Point& delta); 91 | 92 | void simplify(); 93 | 94 | bool point_is_junction(const Point &p) const; 95 | bool point_on_wire(const Point &p) const; 96 | ModelWireSegment *segment_at_point(const Point &p) const; 97 | 98 | void remove_segment(ModelWireSegment *segment); 99 | segment_set_t reachable_segments(ModelWireSegment *from_segment) const; 100 | bool in_one_piece() const; 101 | 102 | private: 103 | void remove_junction(ModelWireJunction *junction); 104 | void remove_segment_from_junction(ModelWireJunction *junction, ModelWireSegment *segment); 105 | void remove_redundant_segment(ModelWireSegment *segment); 106 | 107 | private: 108 | uint32_t m_id; 109 | pin_id_container_t m_pins; 110 | junction_container_t m_junctions; 111 | segment_container_t m_segments; 112 | }; 113 | 114 | 115 | } // namespace lsim 116 | 117 | 118 | #endif // LSIM_MODEL_WIRE_H -------------------------------------------------------------------------------- /src/gui/ui_popup_files.cpp: -------------------------------------------------------------------------------- 1 | // ui_popup_files.cpp - Johan Smet - BSD-3-Clause (see LICENSE) 2 | // 3 | // file related popups 4 | 5 | #include "ui_popup_files.h" 6 | #include "imgui/imgui.h" 7 | #include "lsim_context.h" 8 | 9 | #include "cute_files.h" 10 | 11 | #include 12 | #include 13 | 14 | namespace { 15 | 16 | constexpr const char* POPUP_FILE_SELECTOR = "Select file"; 17 | constexpr const char* POPUP_FILENAME_ENTRY = "Filename entry"; 18 | 19 | // famous last words: there should only ever by one file selection popup open at a time, so why bother. 20 | std::vector file_list; 21 | lsim::gui::on_select_func_t on_select_callback = nullptr; 22 | lsim::gui::on_select_func_t on_close_callback = nullptr; 23 | 24 | void r_scan_dir(const char *dir_path) { 25 | std::vector entries; 26 | cf_dir_t dir; 27 | 28 | for (cf_dir_open(&dir, dir_path); dir.has_next != 0; cf_dir_next(&dir)) { 29 | cf_file_t file; 30 | cf_read_file(&dir, &file); 31 | 32 | if (file.is_dir != 0 && file.name[0] == '.') { 33 | continue; 34 | } 35 | 36 | if (file.is_reg != 0 && cf_match_ext(&file, ".lsim") == 0) { 37 | continue; 38 | } 39 | 40 | entries.push_back(file); 41 | } 42 | 43 | std::sort(entries.begin(), entries.end(), [](const auto &a, const auto &b) { 44 | if (a.is_dir && !b.is_dir) { 45 | return true; 46 | } 47 | 48 | if (!a.is_dir && b.is_dir) { 49 | return false; 50 | } 51 | 52 | return strcmp(a.name, b.name) < 0; 53 | }); 54 | 55 | for (const auto &entry : entries) { 56 | std::string name = entry.path; 57 | //name += '/' + entry.name; 58 | 59 | if (entry.is_dir != 0) { 60 | r_scan_dir(name.c_str()); 61 | } else { 62 | file_list.push_back(name); 63 | } 64 | } 65 | 66 | cf_dir_close(&dir); 67 | } 68 | 69 | } // unnamed namespace 70 | 71 | namespace lsim { 72 | 73 | namespace gui { 74 | 75 | void ui_file_selector_open(LSimContext *context, on_select_func_t on_select) { 76 | file_list.clear(); 77 | for (size_t idx = 0; idx < context->num_folders(); ++idx) { 78 | r_scan_dir(context->folder_path(idx).c_str()); 79 | } 80 | 81 | on_select_callback = move(on_select); 82 | 83 | ImGui::OpenPopup(POPUP_FILE_SELECTOR); 84 | } 85 | 86 | void ui_file_selector_define() { 87 | 88 | ImGui::SetNextWindowPos(ImGui::GetMousePos(), ImGuiCond_Appearing); 89 | if (ImGui::BeginPopupModal(POPUP_FILE_SELECTOR)) { 90 | ImGui::Text("Click to select library"); 91 | ImGui::Separator(); 92 | 93 | for (const auto &entry : file_list) { 94 | if (ImGui::Selectable(entry.c_str())) { 95 | if (on_select_callback) { 96 | on_select_callback(entry); 97 | } 98 | break; 99 | } 100 | } 101 | 102 | ImGui::Separator(); 103 | if (ImGui::Button("Cancel")) { 104 | ImGui::CloseCurrentPopup(); 105 | } 106 | 107 | ImGui::EndPopup(); 108 | } 109 | } 110 | 111 | void ui_filename_entry_open(on_select_func_t on_close) { 112 | on_close_callback = move(on_close); 113 | ImGui::OpenPopup(POPUP_FILENAME_ENTRY); 114 | } 115 | 116 | void ui_filename_entry_define() { 117 | static char buffer[512] = ""; 118 | 119 | ImGui::SetNextWindowContentWidth(300); 120 | if (ImGui::BeginPopupModal(POPUP_FILENAME_ENTRY)) { 121 | ImGui::InputText("Filename", buffer, sizeof(buffer) / sizeof(buffer[0])); 122 | 123 | auto close_popup = []() { 124 | buffer[0] = '\0'; 125 | ImGui::CloseCurrentPopup(); 126 | }; 127 | 128 | if (ImGui::Button("Ok")) { 129 | if (on_close_callback) { 130 | on_close_callback(buffer); 131 | } 132 | close_popup(); 133 | } 134 | ImGui::SameLine(); 135 | if (ImGui::Button("Cancel")) { 136 | close_popup(); 137 | } 138 | ImGui::EndPopup(); 139 | } 140 | 141 | } 142 | 143 | } // namespace lsim::gui 144 | 145 | } // namespace lsim 146 | -------------------------------------------------------------------------------- /src/model_property.h: -------------------------------------------------------------------------------- 1 | // model_property.h - Johan Smet - BSD-3-Clause (see LICENSE) 2 | // 3 | // Key/value pair to store extra information about specific components 4 | 5 | #ifndef LSIM_MODEL_PROPERTY_H 6 | #define LSIM_MODEL_PROPERTY_H 7 | 8 | #include 9 | #include 10 | #include "sim_types.h" 11 | 12 | namespace lsim { 13 | 14 | class Property { 15 | public: 16 | using uptr_t = std::unique_ptr; 17 | public: 18 | Property(const char *key) : m_key(key) {} 19 | virtual ~Property() = default; 20 | virtual const char *key() const {return m_key.c_str();}; 21 | virtual uptr_t clone() const = 0; 22 | 23 | // value accessors 24 | virtual std::string value_as_string() const = 0; 25 | virtual int64_t value_as_integer() const = 0; 26 | virtual bool value_as_boolean() const = 0; 27 | virtual Value value_as_lsim_value() const = 0; 28 | 29 | // value modifiers 30 | virtual void value(const char *val) = 0; 31 | virtual void value(int64_t val) = 0; 32 | virtual void value(bool val) = 0; 33 | virtual void value(Value val) = 0; 34 | 35 | private: 36 | std::string m_key; 37 | }; 38 | 39 | class StringProperty : public Property { 40 | public: 41 | StringProperty(const char *key, const char *value); 42 | uptr_t clone() const override; 43 | 44 | // value accessors 45 | std::string value_as_string() const override; 46 | int64_t value_as_integer() const override; 47 | bool value_as_boolean() const override; 48 | Value value_as_lsim_value() const override; 49 | 50 | // value modifiers 51 | void value(const char *val) override; 52 | void value(int64_t val) override; 53 | void value(bool val) override; 54 | void value(Value val) override; 55 | 56 | private: 57 | std::string m_value; 58 | }; 59 | 60 | class IntegerProperty : public Property { 61 | public: 62 | IntegerProperty(const char *key, int64_t value); 63 | uptr_t clone() const override; 64 | 65 | // value accessors 66 | std::string value_as_string() const override; 67 | int64_t value_as_integer() const override; 68 | bool value_as_boolean() const override; 69 | Value value_as_lsim_value() const override; 70 | 71 | // value modifiers 72 | void value(const char *val) override; 73 | void value(int64_t val) override; 74 | void value(bool val) override; 75 | void value(Value val) override; 76 | 77 | private: 78 | int64_t m_value; 79 | }; 80 | 81 | class BoolProperty : public Property { 82 | public: 83 | BoolProperty(const char *key, bool value); 84 | uptr_t clone() const override; 85 | 86 | // value accessors 87 | std::string value_as_string() const override; 88 | int64_t value_as_integer() const override; 89 | bool value_as_boolean() const override; 90 | Value value_as_lsim_value() const override; 91 | 92 | // value modifiers 93 | void value(const char *val) override; 94 | void value(int64_t val) override; 95 | void value(bool val) override; 96 | void value(Value val) override; 97 | 98 | private: 99 | bool m_value; 100 | }; 101 | 102 | class ValueProperty : public Property { 103 | public: 104 | ValueProperty(const char *key, Value value); 105 | uptr_t clone() const override; 106 | 107 | // value accessors 108 | std::string value_as_string() const override; 109 | int64_t value_as_integer() const override; 110 | bool value_as_boolean() const override; 111 | Value value_as_lsim_value() const override; 112 | 113 | // value modifiers 114 | void value(const char *val) override; 115 | void value(int64_t val) override; 116 | void value(bool val) override; 117 | void value(Value val) override; 118 | 119 | private: 120 | Value m_value; 121 | }; 122 | 123 | inline Property::uptr_t make_property(const char *key, const char *value) { 124 | return std::make_unique(key, value); 125 | } 126 | 127 | inline Property::uptr_t make_property(const char *key, int64_t value) { 128 | return std::make_unique(key, value); 129 | } 130 | 131 | inline Property::uptr_t make_property(const char *key, bool value) { 132 | return std::make_unique(key, value); 133 | } 134 | 135 | inline Property::uptr_t make_property(const char *key, Value value) { 136 | return std::make_unique(key, value); 137 | } 138 | 139 | } // namespace lsim 140 | 141 | #endif // LSIM_MODEL_PROPERTY_H -------------------------------------------------------------------------------- /src/tools/rom_8bit_ctrl.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # rom_8bit_ctrl.py: microcode ROM for the control logic module of the minimal 8-bit computer 4 | # - input : [0-2] = microcode sub step 5 | # [3-7] = opcode 6 | # [8-8] = zero flag 7 | # [9-9] = carry flag 8 | # - output : 16 bits = the control signals 9 | # [00] = AI : register-A input enable 10 | # [01] = AO : register-A output enable 11 | # [02] = BI : register-B input enable 12 | # [03] = BO : register-B output enable 13 | # [04] = RI : RAM input enable 14 | # [05] = RO : RAM output enable 15 | # [06] = INC : increment program counter 16 | # [07] = JMP : load program counter 17 | # [08] = IF : Instruction decoder - instruction fetch 18 | # [09] = DL : Instruction decoder - data load 19 | # [10] = DO : Display Output 20 | # [11] = HLT : stop clock 21 | # [12] = CE : ALU - enable compute 22 | # [13] = CO : ALU - output compute result to databus 23 | # [14] = free - not used 24 | # [15] = RS : reset control logic counter 25 | # 26 | 27 | import rom_8bit_common as c 28 | 29 | SIGNAL_AI = 1 << 0 30 | SIGNAL_AO = 1 << 1 31 | SIGNAL_BI = 1 << 2 32 | SIGNAL_BO = 1 << 3 33 | SIGNAL_RI = 1 << 4 34 | SIGNAL_RO = 1 << 5 35 | SIGNAL_INC = 1 << 6 36 | SIGNAL_JMP = 1 << 7 37 | SIGNAL_IF = 1 << 8 38 | SIGNAL_DL = 1 << 9 39 | SIGNAL_DO = 1 << 10 40 | SIGNAL_HLT = 1 << 11 41 | SIGNAL_CE = 1 << 12 42 | SIGNAL_CO = 1 << 13 43 | SIGNAL_RS = 1 << 15 44 | 45 | rom_data = [0 for i in range(2**10)] 46 | cur_addr = {} 47 | 48 | def addr(op, step, zf = 0, cf = 0): 49 | return (step & 0b111) + ((op & 0b11111) << 3) + ((zf & 1) << 8) + ((cf & 1) << 9) 50 | 51 | def emit(signals): 52 | for zf in cur_addr['zf']: 53 | for cf in cur_addr['cf']: 54 | rom_data[addr(cur_addr['op'], cur_addr['step'], zf, cf)] = signals 55 | cur_addr['step'] += 1 56 | 57 | def opcode_start(op, zf = [0,1], cf = [0,1]): 58 | cur_addr['op'] = op 59 | cur_addr['step'] = 0 60 | cur_addr['cf'] = cf 61 | cur_addr['zf'] = zf 62 | 63 | emit(SIGNAL_IF) 64 | emit(SIGNAL_INC) 65 | 66 | def opcode_NOP(): 67 | opcode_start(c.OPCODE_NOP) 68 | emit(SIGNAL_RS) 69 | 70 | def opcode_LDA(): 71 | opcode_start(c.OPCODE_LDA) 72 | emit(SIGNAL_AI | SIGNAL_RO) 73 | emit(SIGNAL_RS) 74 | 75 | def opcode_LDB(): 76 | opcode_start(c.OPCODE_LDB) 77 | emit(SIGNAL_BI | SIGNAL_RO) 78 | emit(SIGNAL_RS) 79 | 80 | def opcode_LTA(): 81 | opcode_start(c.OPCODE_LTA) 82 | emit(SIGNAL_DL | SIGNAL_AI) 83 | emit(SIGNAL_INC) 84 | emit(SIGNAL_RS) 85 | 86 | def opcode_LTB(): 87 | opcode_start(c.OPCODE_LTB) 88 | emit(SIGNAL_DL | SIGNAL_BI) 89 | emit(SIGNAL_INC) 90 | emit(SIGNAL_RS) 91 | 92 | def opcode_PRA(): 93 | opcode_start(c.OPCODE_PRA) 94 | emit(SIGNAL_AO | SIGNAL_DO) 95 | emit(SIGNAL_RS) 96 | 97 | def opcode_STA(): 98 | opcode_start(c.OPCODE_STA) 99 | emit(SIGNAL_AO | SIGNAL_RI) 100 | emit(SIGNAL_RS) 101 | 102 | def opcode_STB(): 103 | opcode_start(c.OPCODE_STB) 104 | emit(SIGNAL_BO | SIGNAL_RI) 105 | emit(SIGNAL_RS) 106 | 107 | def opcode_HLT(): 108 | opcode_start(c.OPCODE_HLT) 109 | emit(SIGNAL_HLT) 110 | emit(SIGNAL_RS) 111 | 112 | def opcode_JMP(): 113 | opcode_start(c.OPCODE_JMP) 114 | emit(SIGNAL_DL | SIGNAL_JMP | SIGNAL_RS) 115 | 116 | def opcode_JZ(): 117 | opcode_start(c.OPCODE_JZ, zf=[0]) 118 | emit(SIGNAL_INC | SIGNAL_RS) 119 | 120 | opcode_start(c.OPCODE_JZ, zf=[1]) 121 | emit(SIGNAL_DL | SIGNAL_JMP | SIGNAL_RS) 122 | 123 | def opcode_JC(): 124 | opcode_start(c.OPCODE_JC, cf=[0]) 125 | emit(SIGNAL_INC | SIGNAL_RS) 126 | 127 | opcode_start(c.OPCODE_JC, cf=[1]) 128 | emit(SIGNAL_DL | SIGNAL_JMP | SIGNAL_RS) 129 | 130 | def opcode_alu(op): 131 | opcode_start(op) 132 | emit(SIGNAL_CE) 133 | emit(SIGNAL_CO | SIGNAL_AI) 134 | emit(SIGNAL_RS) 135 | 136 | def main(): 137 | opcode_NOP() 138 | opcode_LDA() 139 | opcode_LDB() 140 | opcode_LTA() 141 | opcode_LTB() 142 | opcode_PRA() 143 | opcode_STA() 144 | opcode_STB() 145 | opcode_JMP() 146 | opcode_JZ() 147 | opcode_JC() 148 | opcode_HLT() 149 | opcode_alu(c.OPCODE_ADD) 150 | opcode_alu(c.OPCODE_SUB) 151 | c.write_binary("ctrl_8bit.bin", rom_data, 16) 152 | 153 | if __name__ == "__main__": 154 | main() -------------------------------------------------------------------------------- /src/gui/ui_window_main.cpp: -------------------------------------------------------------------------------- 1 | // ui_window_main.cpp - Johan Smet - BSD-3-Clause (see LICENSE) 2 | 3 | #include "ui_window_main.h" 4 | #include "imgui_ex.h" 5 | 6 | #include "component_draw.h" 7 | #include "component_widget.h" 8 | #include "circuit_editor.h" 9 | 10 | #include "lsim_context.h" 11 | #include "sim_circuit.h" 12 | #include "ui_context.h" 13 | 14 | namespace { 15 | 16 | lsim::LSimContext lsim_context; 17 | lsim::gui::UIContext ui_context(&lsim_context); 18 | 19 | } // unnamed namespace 20 | 21 | namespace lsim { 22 | 23 | namespace gui { 24 | 25 | void ui_panel_component(UIContext* ui_context); 26 | void ui_panel_circuit(UIContext* ui_context); 27 | void ui_panel_library(UIContext* ui_context); 28 | void ui_panel_property(UIContext* ui_context); 29 | 30 | void main_window_setup(const char *circuit_file) { 31 | component_register_basic(); 32 | component_register_extra(); 33 | component_register_gates(); 34 | component_register_input_output(); 35 | 36 | ui_context.lsim_context()->add_folder("examples", "./examples"); 37 | 38 | // try to load the circuit specified on the command line 39 | if (circuit_file != nullptr) { 40 | ui_context.circuit_library_load(circuit_file); 41 | } 42 | } 43 | 44 | void main_window_update() { 45 | auto sim = ui_context.lsim_context()->sim(); 46 | 47 | /////////////////////////////////////////////////////////////////////////// 48 | // 49 | // control window 50 | // 51 | 52 | ImGui::SetNextWindowPos({0,0}, ImGuiCond_FirstUseEver); 53 | ImGui::SetNextWindowSize({268, ImGui::GetIO().DisplaySize.y}, ImGuiCond_FirstUseEver); 54 | 55 | 56 | ImGui::Begin("Control"); 57 | 58 | // Library management 59 | ui_panel_library(&ui_context); 60 | 61 | // Circuit management 62 | ImGui::Spacing(); 63 | if (ImGui::CollapsingHeader("Circuits", ImGuiTreeNodeFlags_DefaultOpen)) { 64 | ui_panel_circuit(&ui_context); 65 | } 66 | 67 | // Component pallette 68 | ImGui::Spacing(); 69 | if (ImGui::CollapsingHeader("Components", ImGuiTreeNodeFlags_DefaultOpen)) { 70 | ui_panel_component(&ui_context); 71 | } 72 | 73 | // Properties 74 | ImGui::Spacing(); 75 | if (ImGui::CollapsingHeader("Properties", ImGuiTreeNodeFlags_DefaultOpen)) { 76 | ui_panel_property(&ui_context); 77 | } 78 | 79 | ImGui::End(); 80 | 81 | /////////////////////////////////////////////////////////////////////////// 82 | // 83 | // main circuit window 84 | // 85 | 86 | static bool sim_running = false; 87 | static bool sim_single_step = false; 88 | static int cycles_per_frame = 5; 89 | 90 | ImGui::SetNextWindowPos({268, 0}, ImGuiSetCond_FirstUseEver); 91 | ImGui::SetNextWindowSize({ImGui::GetIO().DisplaySize.x-268, ImGui::GetIO().DisplaySize.y}, ImGuiSetCond_FirstUseEver); 92 | ImGui::Begin("Circuit", nullptr, ImGuiWindowFlags_NoScrollWithMouse); 93 | 94 | if (ImGui::RadioButton("Editor", !ui_context.circuit_editor()->is_simulating())) { 95 | ui_context.simulation_stop(); 96 | } 97 | ImGui::SameLine(); 98 | 99 | if (ImGui::RadioButton("Simulation", ui_context.circuit_editor()->is_simulating())) { 100 | ui_context.simulation_start(); 101 | } 102 | 103 | if (ui_context.circuit_editor()->is_simulating()) { 104 | ImGui::SameLine(); 105 | ImGui::Checkbox("Run simulation", &sim_running); 106 | ImGui::SameLine(); 107 | if (ImGui::Button("Reset simulation")) { 108 | sim->init(); 109 | } 110 | ImGui::SameLine(); 111 | sim_single_step = ImGui::Button("Step"); 112 | ImGui::SameLine(); 113 | ImGui::SetNextItemWidth(80); 114 | if (ImGui::InputInt("Cycles per frame", &cycles_per_frame)) { 115 | if (cycles_per_frame <= 0) { 116 | cycles_per_frame = 1; 117 | } 118 | } 119 | } 120 | 121 | if (sim_single_step) { 122 | sim->step(); 123 | sim_single_step = false; 124 | } else if (sim_running && ui_context.sim_circuit() != nullptr) { 125 | for (int i = 0; i < cycles_per_frame; ++i) { 126 | sim->step(); 127 | } 128 | } 129 | 130 | if (ui_context.circuit_editor() != nullptr) { 131 | ui_context.circuit_editor()->refresh(&ui_context); 132 | } 133 | 134 | ImGui::End(); 135 | 136 | // display windows for drill-downs 137 | ui_context.foreach_sub_circuit_view([](auto* drill_down) { 138 | bool keep_open = true; 139 | 140 | ImGui::SetNextWindowSize(drill_down->circuit_dimensions() + Point(50,50), ImGuiCond_Appearing); 141 | ImGui::Begin(drill_down->sim_circuit()->name(), &keep_open, ImGuiWindowFlags_NoScrollWithMouse); 142 | drill_down->refresh(&ui_context); 143 | ImGui::End(); 144 | 145 | return keep_open; 146 | }); 147 | } 148 | 149 | } // namespace lsim::gui 150 | 151 | } // namespace lsim -------------------------------------------------------------------------------- /src/model_circuit.h: -------------------------------------------------------------------------------- 1 | // model_circuit.h - Johan Smet - BSD-3-Clause (see LICENSE) 2 | // 3 | // describe the composition of a logic circuit 4 | 5 | #ifndef LSIM_MODEL_CIRCUIT_H 6 | #define LSIM_MODEL_CIRCUIT_H 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "model_component.h" 14 | #include "model_wire.h" 15 | 16 | namespace lsim { 17 | 18 | class ModelCircuit { 19 | public: 20 | using uptr_t = std::unique_ptr; 21 | using wire_lut_t = std::unordered_map; 22 | public: 23 | ModelCircuit(const char *name, class LSimContext *context, class ModelCircuitLibrary *ref_lib); 24 | ModelCircuit(const ModelCircuit &) = delete; 25 | 26 | class LSimContext *context() const {return m_context;} 27 | class ModelCircuitLibrary *lib() const {return m_lib;} 28 | 29 | // name 30 | const std::string &name() const {return m_name;} 31 | std::string qualified_name() const; 32 | void change_name(const char *name); 33 | 34 | // components 35 | protected: 36 | ModelComponent *create_component(ComponentType type, uint32_t input_pins, uint32_t output_pins = 1, uint32_t control_pins = 0); 37 | ModelComponent *create_component(const char *circuit_name, uint32_t input_pins, uint32_t output_pins); 38 | public: 39 | ModelComponent *component_by_id(uint32_t id); 40 | std::vector component_ids() const; 41 | std::vector component_ids_of_type(ComponentType type) const; 42 | void disconnect_component(uint32_t id); 43 | void remove_component(uint32_t id); 44 | void sync_sub_circuit_components(); 45 | ModelComponent *paste_component(ModelComponent *comp); 46 | 47 | // connections 48 | ModelWire *create_wire(); 49 | ModelWire *connect(pin_id_t pin_a, pin_id_t pin_b); 50 | void disconnect_pin(pin_id_t pin); 51 | std::vector wire_ids() const; 52 | ModelWire *wire_by_id(uint32_t id) const; 53 | const wire_lut_t &wires() const {return m_wires;} 54 | void remove_wire(uint32_t id); 55 | 56 | // ports 57 | void rebuild_port_list(); 58 | void change_port_pin_count(uint32_t comp_id, uint32_t new_count); 59 | pin_id_t port_by_name(const char *name) const; 60 | pin_id_t port_by_index(bool input, size_t index) const; 61 | const std::string &port_name(bool input, size_t index) const; 62 | uint32_t num_input_ports() const {return static_cast(m_input_ports.size());} 63 | uint32_t num_output_ports() const {return static_cast(m_output_ports.size());} 64 | 65 | // specialized component creation functions 66 | ModelComponent *add_connector_in(const char *name, uint32_t data_bits, bool tri_state = false); 67 | ModelComponent *add_connector_out(const char *name, uint32_t data_bits, bool tri_state = false); 68 | ModelComponent *add_constant(Value value); 69 | ModelComponent *add_pull_resistor(Value pull_to); 70 | ModelComponent *add_buffer(uint32_t data_bits); 71 | ModelComponent *add_tristate_buffer(uint32_t data_bits); 72 | ModelComponent *add_and_gate(uint32_t num_inputs); 73 | ModelComponent *add_or_gate(uint32_t num_inputs); 74 | ModelComponent *add_not_gate(); 75 | ModelComponent *add_nand_gate(uint32_t num_inputs); 76 | ModelComponent *add_nor_gate(uint32_t num_inputs); 77 | ModelComponent *add_xor_gate(); 78 | ModelComponent *add_xnor_gate(); 79 | ModelComponent *add_via(const char *name, uint32_t data_bits); 80 | ModelComponent *add_oscillator(uint32_t low_duration, uint32_t high_duration); 81 | ModelComponent *add_7_segment_led(); 82 | ModelComponent *add_sub_circuit(const char *circuit, uint32_t num_inputs, uint32_t num_outputs); 83 | ModelComponent *add_sub_circuit(const char *circuit); 84 | ModelComponent *add_text(const char *text); 85 | 86 | // instantiate into a simulator 87 | std::unique_ptr instantiate(class Simulator *sim, bool top_level = true); 88 | 89 | private: 90 | using component_lut_t = std::unordered_map; 91 | using port_container_t = std::vector; 92 | 93 | private: 94 | class LSimContext *m_context; 95 | class ModelCircuitLibrary *m_lib; 96 | 97 | std::string m_name; 98 | 99 | uint32_t m_component_id; 100 | component_lut_t m_components; 101 | 102 | uint32_t m_wire_id; 103 | wire_lut_t m_wires; 104 | 105 | port_lut_t m_ports_lut; 106 | port_container_t m_input_ports; 107 | port_container_t m_output_ports; 108 | 109 | }; 110 | 111 | } // namespace lsim 112 | 113 | #endif // LSIM_MODEL_CIRCUIT_H -------------------------------------------------------------------------------- /src/gui/ui_panel_component.cpp: -------------------------------------------------------------------------------- 1 | // ui_panel_component_.cpp - Johan Smet - BSD-3-Clause (see LICENSE) 2 | 3 | #include "imgui_ex.h" 4 | 5 | #include "component_icon.h" 6 | #include "colors.h" 7 | #include "circuit_editor.h" 8 | #include "lsim_context.h" 9 | #include "ui_context.h" 10 | 11 | namespace lsim { 12 | 13 | namespace gui { 14 | 15 | void ui_panel_component(UIContext* ui_context) { 16 | 17 | auto circuit_editor = ui_context->circuit_editor(); 18 | auto context = ui_context->lsim_context(); 19 | 20 | auto add_component_button = [circuit_editor](uint32_t component, const char* caption, std::function create_func) { 21 | Point pos = ImGui::GetCursorScreenPos(); 22 | auto draw_list = ImGui::GetWindowDrawList(); 23 | 24 | ImGui::PushID((std::string("lsim_") + caption).c_str()); 25 | if (ImGui::Button("", { 40, 40 })) { 26 | if (!circuit_editor->is_simulating()) { 27 | auto component = create_func(circuit_editor->model_circuit()); 28 | component->set_position({ -200, -200 }); 29 | circuit_editor->ui_create_component(component); 30 | } 31 | } 32 | auto icon = ComponentIcon::cached(component); 33 | if (icon != nullptr) { 34 | icon->draw(pos + Point(20, 20), { 34, 34 }, draw_list, 1, COLOR_COMPONENT_BORDER); 35 | } 36 | ImGuiEx::Text(pos + Point(50, 20), caption, ImGuiEx::TAH_LEFT, ImGuiEx::TAV_CENTER); 37 | ImGui::SetCursorScreenPos(pos + Point(0, 42)); 38 | ImGui::PopID(); 39 | }; 40 | 41 | ImGui::Spacing(); 42 | if (ImGui::TreeNodeEx("Gates", ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_NoTreePushOnOpen)) { 43 | ImGui::BeginGroup(); 44 | ImGui::Indent(); 45 | add_component_button(COMPONENT_AND_GATE, "AND", [](ModelCircuit* circuit) {return circuit->add_and_gate(2); }); 46 | add_component_button(COMPONENT_OR_GATE, "OR", [](ModelCircuit* circuit) {return circuit->add_or_gate(2); }); 47 | add_component_button(COMPONENT_NOT_GATE, "NOT", [](ModelCircuit* circuit) {return circuit->add_not_gate(); }); 48 | add_component_button(COMPONENT_NAND_GATE, "NAND", [](ModelCircuit* circuit) {return circuit->add_nand_gate(2); }); 49 | add_component_button(COMPONENT_NOR_GATE, "NOR", [](ModelCircuit* circuit) {return circuit->add_nor_gate(2); }); 50 | add_component_button(COMPONENT_XOR_GATE, "XOR", [](ModelCircuit* circuit) {return circuit->add_xor_gate(); }); 51 | add_component_button(COMPONENT_XNOR_GATE, "XNOR", [](ModelCircuit* circuit) {return circuit->add_xnor_gate(); }); 52 | add_component_button(COMPONENT_BUFFER, "Buffer", [](ModelCircuit* circuit) {return circuit->add_buffer(1); }); 53 | add_component_button(COMPONENT_TRISTATE_BUFFER, "TriState Buffer", [](ModelCircuit* circuit) {return circuit->add_tristate_buffer(1); }); 54 | ImGui::EndGroup(); 55 | } 56 | 57 | ImGui::Spacing(); 58 | if (ImGui::TreeNodeEx("Various", ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_NoTreePushOnOpen)) { 59 | ImGui::BeginGroup(); 60 | ImGui::Indent(); 61 | add_component_button(COMPONENT_CONNECTOR_IN, "Input", [](ModelCircuit* circuit) {return circuit->add_connector_in("in", 1); }); 62 | add_component_button(COMPONENT_CONNECTOR_OUT, "Output", [](ModelCircuit* circuit) {return circuit->add_connector_out("out", 1); }); 63 | add_component_button(COMPONENT_CONSTANT, "Constant", [](ModelCircuit* circuit) {return circuit->add_constant(VALUE_TRUE); }); 64 | add_component_button(COMPONENT_PULL_RESISTOR, "PullResistor", [](ModelCircuit* circuit) {return circuit->add_pull_resistor(VALUE_TRUE); }); 65 | add_component_button(COMPONENT_VIA, "Via", [](ModelCircuit* circuit) {return circuit->add_via("via", 1); }); 66 | add_component_button(COMPONENT_OSCILLATOR, "Oscillator", [](ModelCircuit* circuit) {return circuit->add_oscillator(5, 5); }); 67 | add_component_button(COMPONENT_TEXT, "Text", [](ModelCircuit* circuit) {return circuit->add_text("text"); }); 68 | ImGui::EndGroup(); 69 | } 70 | 71 | ImGui::Spacing(); 72 | if (ImGui::TreeNodeEx("I/O", ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_NoTreePushOnOpen)) { 73 | ImGui::BeginGroup(); 74 | ImGui::Indent(); 75 | add_component_button(COMPONENT_7_SEGMENT_LED, "7-segment LED", [](ModelCircuit* circuit) {return circuit->add_7_segment_led(); }); 76 | ImGui::EndGroup(); 77 | } 78 | 79 | for (const auto& ref : context->user_library()->references()) { 80 | ImGui::Spacing(); 81 | if (ImGui::TreeNodeEx(ref.c_str(), ImGuiTreeNodeFlags_NoTreePushOnOpen)) { 82 | auto ref_lib = context->library_by_name(ref.c_str()); 83 | 84 | ImGui::BeginGroup(); 85 | ImGui::Indent(); 86 | for (size_t idx = 0; idx < ref_lib->num_circuits(); ++idx) { 87 | auto sub_name = ref_lib->circuit_by_idx(idx)->name(); 88 | auto full_name = ref + "."; 89 | full_name += sub_name; 90 | add_component_button(COMPONENT_SUB_CIRCUIT, sub_name.c_str(), 91 | [full_name](ModelCircuit* circuit) { 92 | return circuit->add_sub_circuit(full_name.c_str()); 93 | }); 94 | } 95 | ImGui::EndGroup(); 96 | } 97 | } 98 | } 99 | 100 | } // namespace lsim::gui 101 | 102 | } // namespace lsim 103 | -------------------------------------------------------------------------------- /src/bench/bench_memory.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import lsimpy 4 | from bench_utils import * 5 | 6 | BYTE_UNDEFINED = [lsimpy.ValueUndefined, lsimpy.ValueUndefined, lsimpy.ValueUndefined, lsimpy.ValueUndefined, 7 | lsimpy.ValueUndefined, lsimpy.ValueUndefined, lsimpy.ValueUndefined, lsimpy.ValueUndefined] 8 | 9 | def test_mem_cell(lsim): 10 | print("* Testing mem_cell") 11 | sim = lsim.sim() 12 | 13 | circuit_desc = lsim.user_library().circuit_by_name('mem_cell') 14 | 15 | pins_D = [circuit_desc.port_by_name(f"D[{i:}]") for i in range(0,8)] 16 | 17 | circuit = circuit_desc.instantiate(sim) 18 | 19 | sim.init() 20 | 21 | for d in range(0, 2**8): 22 | circuit.write_byte(pins_D, d) 23 | circuit.write_port("WE", lsimpy.ValueTrue) 24 | circuit.write_port("CE", lsimpy.ValueTrue) 25 | sim.run_until_stable(2) 26 | circuit.write_port("WE", lsimpy.ValueFalse) 27 | circuit.write_port("CE", lsimpy.ValueFalse) 28 | sim.run_until_stable(2) 29 | circuit.write_pins(pins_D, BYTE_UNDEFINED) 30 | sim.run_until_stable(2) 31 | circuit.write_port("OE", lsimpy.ValueTrue) 32 | circuit.write_port("CE", lsimpy.ValueTrue) 33 | sim.run_until_stable(2) 34 | CHECK(circuit.read_byte(pins_D), d, "D{}".format(d)) 35 | circuit.write_port("OE", lsimpy.ValueFalse) 36 | circuit.write_port("CE", lsimpy.ValueFalse) 37 | sim.run_until_stable(2) 38 | 39 | def test_ram_8byte(lsim): 40 | print("* Testing ram_8byte") 41 | sim = lsim.sim() 42 | 43 | circuit_desc = lsim.user_library().circuit_by_name('ram_8byte') 44 | 45 | pins_D = [circuit_desc.port_by_name(f"D[{i:}]") for i in range(0,8)] 46 | pins_Addr = [circuit_desc.port_by_name(f"Addr[{i:}]") for i in range(0,3)] 47 | 48 | circuit = circuit_desc.instantiate(sim) 49 | sim.init() 50 | 51 | for d in range(0, 2**8): 52 | 53 | for addr in range(0, 2**3): 54 | circuit.write_pins(pins_Addr, addr) 55 | circuit.write_pins(pins_D, (d + addr) % 256) 56 | circuit.write_port("WE", lsimpy.ValueTrue) 57 | circuit.write_port("CE", lsimpy.ValueTrue) 58 | sim.run_until_stable(2) 59 | circuit.write_port("WE", lsimpy.ValueFalse) 60 | circuit.write_port("CE", lsimpy.ValueFalse) 61 | sim.run_until_stable(2) 62 | circuit.write_pins(pins_D, BYTE_UNDEFINED) 63 | sim.run_until_stable(2) 64 | 65 | for addr in range(0, 2**3): 66 | circuit.write_pins(pins_Addr, addr) 67 | circuit.write_port("OE", lsimpy.ValueTrue) 68 | circuit.write_port("CE", lsimpy.ValueTrue) 69 | sim.run_until_stable(2) 70 | expected = (d + addr) % 256 71 | CHECK(circuit.read_byte(pins_D), expected, "D({}) Addr({})".format(d, addr)) 72 | circuit.write_port("OE", lsimpy.ValueFalse) 73 | circuit.write_port("CE", lsimpy.ValueFalse) 74 | sim.run_until_stable(2) 75 | 76 | def test_ram_64byte(lsim): 77 | print("* Testing ram_64byte") 78 | sim = lsim.sim() 79 | 80 | circuit_desc = lsim.user_library().circuit_by_name('ram_64byte') 81 | 82 | pins_D = [circuit_desc.port_by_name(f"D[{i:}]") for i in range(0,8)] 83 | pins_Addr = [circuit_desc.port_by_name(f"Addr[{i:}]") for i in range(0,6)] 84 | 85 | circuit = circuit_desc.instantiate(sim) 86 | sim.init() 87 | 88 | for d in range(0, 2**8): 89 | for addr in range(0, 2**6): 90 | circuit.write_pins(pins_Addr, addr) 91 | circuit.write_pins(pins_D, (d + addr) % 256) 92 | circuit.write_port("WE", lsimpy.ValueTrue) 93 | circuit.write_port("CE", lsimpy.ValueTrue) 94 | sim.run_until_stable(2) 95 | circuit.write_port("WE", lsimpy.ValueFalse) 96 | circuit.write_port("CE", lsimpy.ValueFalse) 97 | sim.run_until_stable(2) 98 | circuit.write_pins(pins_D, BYTE_UNDEFINED) 99 | sim.run_until_stable(2) 100 | 101 | for addr in range(0, 2**6): 102 | circuit.write_pins(pins_Addr, addr) 103 | circuit.write_port("OE", lsimpy.ValueTrue) 104 | circuit.write_port("CE", lsimpy.ValueTrue) 105 | sim.run_until_stable(2) 106 | expected = (d + addr) % 256 107 | CHECK(circuit.read_byte(pins_D), expected, "D({}) Addr({})".format(d, addr)) 108 | circuit.write_port("OE", lsimpy.ValueFalse) 109 | circuit.write_port("CE", lsimpy.ValueFalse) 110 | sim.run_until_stable(2) 111 | 112 | def main(): 113 | lsim = lsimpy.LSimContext() 114 | lsim.add_folder("examples", "../../examples") 115 | 116 | if (not lsim.load_user_library("examples/cpu_8bit/lib_memory.lsim")): 117 | print("Unable to load circuit\n") 118 | exit(-1) 119 | 120 | test_mem_cell(lsim) 121 | test_ram_8byte(lsim) 122 | test_ram_64byte(lsim) 123 | 124 | print_stats() 125 | 126 | if __name__ == "__main__": 127 | main() -------------------------------------------------------------------------------- /src/simulator.h: -------------------------------------------------------------------------------- 1 | // simulator.h - Johan Smet - BSD-3-Clause (see LICENSE) 2 | 3 | #ifndef LSIM_SIMULATOR_H 4 | #define LSIM_SIMULATOR_H 5 | 6 | // includes 7 | #include "sim_component.h" 8 | #include "sim_functions.h" 9 | 10 | 11 | #include 12 | #include 13 | 14 | namespace lsim { 15 | 16 | struct NodeMetadata { 17 | using component_set_t = std::set; 18 | using pin_set_t = std::set; 19 | 20 | NodeMetadata() = default; 21 | 22 | // data 23 | Value m_default = VALUE_UNDEFINED; 24 | component_set_t m_dependents; 25 | pin_container_t m_pins; 26 | pin_set_t m_active_pins; 27 | timestamp_t m_time_dirty_write = 0; 28 | }; 29 | 30 | class Simulator { 31 | public: 32 | Simulator() = default; 33 | Simulator(const Simulator &) = delete; 34 | 35 | // components 36 | SimComponent *create_component(ModelComponent *desc); 37 | void clear_components(); 38 | 39 | // pins 40 | pin_t assign_pin(SimComponent *component, bool used_as_input); 41 | node_t connect_pins(pin_t pin_a, pin_t pin_b); 42 | void clear_pins(); 43 | void pin_set_default(pin_t pin, Value value); 44 | void pin_set_initial_value(pin_t pin, Value value); 45 | void write_pin(pin_t pin, Value value); 46 | Value read_pin(pin_t pin) const; 47 | Value read_pin_current_step(pin_t pin) const; 48 | bool pin_changed_previous_step(pin_t pin) const; 49 | timestamp_t pin_last_change_time(pin_t pin) const; 50 | 51 | node_t pin_node(pin_t pin) const; 52 | Value pin_output_value(pin_t pin) const; 53 | void pin_set_output_value(pin_t pin, Value value); 54 | 55 | // nodes 56 | node_t assign_node(SimComponent *component, bool used_as_input); 57 | void release_node(node_t node_id); 58 | node_t merge_nodes(node_t node_a, node_t node_b); 59 | void clear_nodes(); 60 | 61 | void node_set_default(node_t node_id, Value value); 62 | void node_set_initial_value(node_t node_id, Value value); 63 | 64 | void write_node(node_t node_id, Value value, pin_t from_pin); 65 | Value read_node(node_t node_id) const; 66 | Value read_node_current_step(node_t node_id) const; 67 | 68 | bool node_changed_previous_step(node_t node_id) const; 69 | timestamp_t node_last_change_time(node_t node_id) const; 70 | 71 | bool node_dirty(node_t node_id) const; 72 | 73 | // simulation functions 74 | void register_sim_function(ComponentType comp_type, SimFuncType func_type, simulation_func_t func); 75 | bool component_has_function(ComponentType comp_type, SimFuncType func_type); 76 | 77 | // simulation 78 | void init(); 79 | void step(); 80 | void run_until_stable(size_t stable_ticks); 81 | timestamp_t current_time() const {return m_time;} 82 | 83 | void activate_independent_simulation_func(SimComponent *comp); 84 | void deactivate_independent_simulation_func(SimComponent *comp); 85 | 86 | private: 87 | void postprocess_dirty_nodes(); 88 | 89 | private: 90 | using timestamp_container_t = std::vector; 91 | using component_container_t = std::vector >; 92 | using component_refs_t = std::vector; 93 | using node_metadata_container_t = std::vector; 94 | using sim_func_container_t = std::vector; 95 | 96 | private: 97 | timestamp_t m_time = 0; // current simulation timestamp 98 | 99 | // components 100 | component_container_t m_components; // all simulator components 101 | timestamp_container_t m_input_changed; // timestamp when component was last added to "to simulate" list 102 | component_refs_t m_init_components; // components with an init function 103 | component_refs_t m_independent_components; // components with an input independent update function 104 | component_refs_t m_dirty_components; // components with changed input values 105 | 106 | // pins 107 | node_container_t m_pin_nodes; // node assignment for each pin 108 | value_container_t m_pin_values; // last value written to a pin 109 | 110 | // nodes 111 | node_metadata_container_t m_node_metadata; // assorted metadata 112 | node_container_t m_free_nodes; // list of node-ids that can be reused 113 | value_container_t m_node_values_read; // values of the nodes after the last simulation run 114 | value_container_t m_node_values_write; // values of the nodes in the current simulation run 115 | node_container_t m_dirty_nodes_read; // nodes that were changed in the last simulation run 116 | node_container_t m_dirty_nodes_write; // nodes that were changed in the current simulation run 117 | 118 | timestamp_container_t m_node_write_time; // timestamp when node was last written to 119 | timestamp_container_t m_node_change_time; // timestamp when node last changed value 120 | 121 | // simulation functions 122 | sim_func_container_t m_sim_functions; 123 | }; 124 | 125 | } // namespace lsim 126 | 127 | #endif // LSIM_SIMULATOR_H -------------------------------------------------------------------------------- /src/model_component.cpp: -------------------------------------------------------------------------------- 1 | // model_component.cpp - Johan Smet - BSD-3-Clause (see LICENSE) 2 | // 3 | // description of a single component 4 | 5 | #include "model_component.h" 6 | #include "model_circuit.h" 7 | #include "lsim_context.h" 8 | 9 | #include 10 | 11 | namespace lsim { 12 | 13 | ModelComponent::ModelComponent(ModelCircuit *parent, uint32_t id, ComponentType type, uint32_t inputs, uint32_t outputs, uint32_t controls) : 14 | m_circuit(parent), 15 | m_id(id), 16 | m_type(type), 17 | m_inputs(inputs), 18 | m_outputs(outputs), 19 | m_controls(controls), 20 | m_nested_name(""), 21 | m_nested_circuit(nullptr), 22 | m_position(0,0), 23 | m_angle(0) { 24 | } 25 | 26 | ModelComponent::ModelComponent(ModelCircuit *parent, uint32_t id, const char *circuit_name, uint32_t inputs, uint32_t outputs) : 27 | m_circuit(parent), 28 | m_id(id), 29 | m_type(COMPONENT_SUB_CIRCUIT), 30 | m_inputs(inputs), 31 | m_outputs(outputs), 32 | m_controls(0), 33 | m_nested_name(circuit_name), 34 | m_nested_circuit(nullptr), 35 | m_position(0,0), 36 | m_angle(0) { 37 | } 38 | 39 | pin_id_t ModelComponent::pin_id(uint32_t index) const { 40 | assert(index < m_inputs + m_outputs + m_controls); 41 | return (static_cast(m_id) << 32) | (index & 0xffffffff); 42 | } 43 | 44 | pin_id_t ModelComponent::input_pin_id(uint32_t index) const { 45 | assert(index < m_inputs); 46 | return pin_id(index); 47 | } 48 | 49 | pin_id_t ModelComponent::output_pin_id(uint32_t index) const { 50 | assert(index < m_outputs); 51 | return pin_id(m_inputs + index); 52 | } 53 | 54 | pin_id_t ModelComponent::control_pin_id(uint32_t index) const { 55 | assert(index < m_controls); 56 | return pin_id(m_inputs + m_outputs + index); 57 | } 58 | 59 | void ModelComponent::change_input_pins(uint32_t new_count) { 60 | m_inputs = new_count; 61 | } 62 | 63 | void ModelComponent::change_output_pins(uint32_t new_count) { 64 | m_outputs = new_count; 65 | } 66 | 67 | pin_id_t ModelComponent::port_by_name(const char *name) const { 68 | assert(m_nested_circuit != nullptr); 69 | auto found = m_port_lut.find(name); 70 | if (found != m_port_lut.end()) { 71 | return found->second; 72 | } 73 | return PIN_ID_INVALID; 74 | } 75 | 76 | void ModelComponent::add_property(Property::uptr_t &&prop) { 77 | m_properties[prop->key()] = std::move(prop); 78 | } 79 | 80 | Property *ModelComponent::property(const char *key) { 81 | auto result = m_properties.find(key); 82 | if (result != m_properties.end()) { 83 | return result->second.get(); 84 | } 85 | return nullptr; 86 | } 87 | 88 | std::string ModelComponent::property_value(const char *key, const char *def_value) { 89 | auto result = property(key); 90 | return (result != nullptr) ? result->value_as_string() : def_value; 91 | } 92 | 93 | int64_t ModelComponent::property_value(const char *key, int64_t def_value) { 94 | auto result = property(key); 95 | return (result != nullptr) ? result->value_as_integer() : def_value; 96 | } 97 | 98 | bool ModelComponent::property_value(const char *key, bool def_value) { 99 | auto result = property(key); 100 | return (result != nullptr) ? result->value_as_boolean() : def_value; 101 | } 102 | 103 | Value ModelComponent::property_value(const char *key, Value def_value) { 104 | auto result = property(key); 105 | return (result != nullptr) ? result->value_as_lsim_value() : def_value; 106 | } 107 | 108 | void ModelComponent::set_position(const Point &pos) { 109 | m_position = pos; 110 | } 111 | 112 | void ModelComponent::set_angle(int angle) { 113 | m_angle = angle; 114 | } 115 | 116 | bool ModelComponent::sync_nested_circuit(LSimContext *lsim_context) { 117 | 118 | m_nested_circuit = lsim_context->find_circuit(m_nested_name.c_str(), m_circuit->lib()); 119 | if (m_nested_circuit == nullptr) { 120 | return false; 121 | } 122 | 123 | m_inputs = m_nested_circuit->num_input_ports(); 124 | m_outputs = m_nested_circuit->num_output_ports(); 125 | 126 | m_port_lut.clear(); 127 | 128 | for (auto idx = 0u; idx < m_inputs; ++idx) { 129 | m_port_lut[m_nested_circuit->port_name(true, idx)] = input_pin_id(idx); 130 | } 131 | 132 | for (auto idx = 0u; idx < m_outputs; ++idx) { 133 | m_port_lut[m_nested_circuit->port_name(false, idx)] = output_pin_id(idx); 134 | } 135 | 136 | return true; 137 | } 138 | 139 | ModelComponent::uptr_t ModelComponent::copy() const { 140 | auto clone = std::make_unique(nullptr, -1, m_type, m_inputs, m_outputs, m_controls); 141 | clone->m_nested_name = m_nested_name; 142 | clone->m_nested_circuit = m_nested_circuit; 143 | clone->m_port_lut = m_port_lut; 144 | for (const auto &prop_it : m_properties) { 145 | clone->add_property(prop_it.second->clone()); 146 | } 147 | clone->m_position = m_position; 148 | clone->m_angle = m_angle; 149 | 150 | return std::move(clone); 151 | } 152 | 153 | void ModelComponent::integrate_into_circuit(ModelCircuit *circuit, uint32_t id) { 154 | m_circuit = circuit; 155 | m_id = id; 156 | } 157 | 158 | } // namespace lsim -------------------------------------------------------------------------------- /examples/tristate.circ: -------------------------------------------------------------------------------- 1 | 2 | 3 | This file is intended to be loaded by Logisim-evolution (https://github.com/reds-heig/logisim-evolution). 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | addr/data: 8 8 18 | 0 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- 26 | -- HEIG-VD, institute REDS, 1400 Yverdon-les-Bains 27 | -- Project : 28 | -- File : 29 | -- Autor : 30 | -- Date : 31 | -- 32 | -------------------------------------------------------------------------------- 33 | -- Description : 34 | -- 35 | -------------------------------------------------------------------------------- 36 | 37 | library ieee; 38 | use ieee.std_logic_1164.all; 39 | --use ieee.numeric_std.all; 40 | 41 | entity VHDL_Component is 42 | port( 43 | ------------------------------------------------------------------------------ 44 | --Insert input ports below 45 | horloge_i : in std_logic; -- input bit example 46 | val_i : in std_logic_vector(3 downto 0); -- input vector example 47 | ------------------------------------------------------------------------------ 48 | --Insert output ports below 49 | max_o : out std_logic; -- output bit example 50 | cpt_o : out std_logic_Vector(3 downto 0) -- output vector example 51 | ); 52 | end VHDL_Component; 53 | 54 | -------------------------------------------------------------------------------- 55 | --Complete your VHDL description below 56 | architecture type_architecture of VHDL_Component is 57 | 58 | 59 | begin 60 | 61 | 62 | end type_architecture; 63 | 64 | 65 | 66 | 67 | 68 | library ieee; 69 | use ieee.std_logic_1164.all; 70 | 71 | entity TCL_Generic is 72 | port( 73 | --Insert input ports below 74 | horloge_i : in std_logic; -- input bit example 75 | val_i : in std_logic_vector(3 downto 0); -- input vector example 76 | 77 | --Insert output ports below 78 | max_o : out std_logic; -- output bit example 79 | cpt_o : out std_logic_Vector(3 downto 0) -- output vector example 80 | ); 81 | end TCL_Generic; 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 |
95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | -------------------------------------------------------------------------------- /src/gui/imgui/imconfig.h: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // COMPILE-TIME OPTIONS FOR DEAR IMGUI 3 | // Runtime options (clipboard callbacks, enabling various features, etc.) can generally be set via the ImGuiIO structure. 4 | // You can use ImGui::SetAllocatorFunctions() before calling ImGui::CreateContext() to rewire memory allocation functions. 5 | //----------------------------------------------------------------------------- 6 | // A) You may edit imconfig.h (and not overwrite it when updating imgui, or maintain a patch/branch with your modifications to imconfig.h) 7 | // B) or add configuration directives in your own file and compile with #define IMGUI_USER_CONFIG "myfilename.h" 8 | // If you do so you need to make sure that configuration settings are defined consistently _everywhere_ dear imgui is used, which include 9 | // the imgui*.cpp files but also _any_ of your code that uses imgui. This is because some compile-time options have an affect on data structures. 10 | // Defining those options in imconfig.h will ensure every compilation unit gets to see the same data structure layouts. 11 | // Call IMGUI_CHECKVERSION() from your .cpp files to verify that the data structures your files are using are matching the ones imgui.cpp is using. 12 | //----------------------------------------------------------------------------- 13 | 14 | #pragma once 15 | 16 | //---- Define assertion handler. Defaults to calling assert(). 17 | //#define IM_ASSERT(_EXPR) MyAssert(_EXPR) 18 | //#define IM_ASSERT(_EXPR) ((void)(_EXPR)) // Disable asserts 19 | 20 | //---- Define attributes of all API symbols declarations, e.g. for DLL under Windows. 21 | //#define IMGUI_API __declspec( dllexport ) 22 | //#define IMGUI_API __declspec( dllimport ) 23 | 24 | //---- Don't define obsolete functions/enums names. Consider enabling from time to time after updating to avoid using soon-to-be obsolete function/names. 25 | //#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS 26 | 27 | //---- Don't implement demo windows functionality (ShowDemoWindow()/ShowStyleEditor()/ShowUserGuide() methods will be empty) 28 | //---- It is very strongly recommended to NOT disable the demo windows during development. Please read the comments in imgui_demo.cpp. 29 | //#define IMGUI_DISABLE_DEMO_WINDOWS 30 | 31 | //---- Don't implement some functions to reduce linkage requirements. 32 | //#define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS // [Win32] Don't implement default clipboard handler. Won't use and link with OpenClipboard/GetClipboardData/CloseClipboard etc. 33 | //#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS // [Win32] Don't implement default IME handler. Won't use and link with ImmGetContext/ImmSetCompositionWindow. 34 | //#define IMGUI_DISABLE_WIN32_FUNCTIONS // [Win32] Won't use and link with any Win32 function. 35 | //#define IMGUI_DISABLE_FORMAT_STRING_FUNCTIONS // Don't implement ImFormatString/ImFormatStringV so you can implement them yourself if you don't want to link with vsnprintf. 36 | //#define IMGUI_DISABLE_MATH_FUNCTIONS // Don't implement ImFabs/ImSqrt/ImPow/ImFmod/ImCos/ImSin/ImAcos/ImAtan2 wrapper so you can implement them yourself. Declare your prototypes in imconfig.h. 37 | //#define IMGUI_DISABLE_DEFAULT_ALLOCATORS // Don't implement default allocators calling malloc()/free() to avoid linking with them. You will need to call ImGui::SetAllocatorFunctions(). 38 | 39 | //---- Include imgui_user.h at the end of imgui.h as a convenience 40 | //#define IMGUI_INCLUDE_IMGUI_USER_H 41 | 42 | //---- Pack colors to BGRA8 instead of RGBA8 (to avoid converting from one to another) 43 | //#define IMGUI_USE_BGRA_PACKED_COLOR 44 | 45 | //---- Avoid multiple STB libraries implementations, or redefine path/filenames to prioritize another version 46 | // By default the embedded implementations are declared static and not available outside of imgui cpp files. 47 | //#define IMGUI_STB_TRUETYPE_FILENAME "my_folder/stb_truetype.h" 48 | //#define IMGUI_STB_RECT_PACK_FILENAME "my_folder/stb_rect_pack.h" 49 | //#define IMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION 50 | //#define IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION 51 | 52 | //---- Define constructor and implicit cast operators to convert back<>forth between your math types and ImVec2/ImVec4. 53 | // This will be inlined as part of ImVec2 and ImVec4 class declarations. 54 | #include "algebra.h" 55 | 56 | #define IM_VEC2_CLASS_EXTRA \ 57 | ImVec2(const lsim::Point &p) { x = p.x; y = p.y;} \ 58 | operator lsim::Point() const { return lsim::Point(x, y);} 59 | 60 | /* 61 | #define IM_VEC2_CLASS_EXTRA \ 62 | ImVec2(const MyVec2& f) { x = f.x; y = f.y; } \ 63 | operator MyVec2() const { return MyVec2(x,y); } 64 | 65 | #define IM_VEC4_CLASS_EXTRA \ 66 | ImVec4(const MyVec4& f) { x = f.x; y = f.y; z = f.z; w = f.w; } \ 67 | operator MyVec4() const { return MyVec4(x,y,z,w); } 68 | */ 69 | 70 | //---- Use 32-bit vertex indices (default is 16-bit) to allow meshes with more than 64K vertices. Render function needs to support it. 71 | //#define ImDrawIdx unsigned int 72 | 73 | //---- Tip: You can add extra functions within the ImGui:: namespace, here or in your own headers files. 74 | /* 75 | namespace ImGui 76 | { 77 | void MyFunction(const char* name, const MyMatrix44& v); 78 | } 79 | */ 80 | -------------------------------------------------------------------------------- /docs/using_lsim_gui.md: -------------------------------------------------------------------------------- 1 | # Using the LSim GUI 2 | 3 | When you open LSim you are greated with two windows: a "Control" window and a "Circuit" window. As the names imply the Control-window contains the interface elements to control the LSim circuit and the Circuit-windows is the canvas on which the circuit is built. 4 | 5 | ![LSim UI](lsim_main.png) 6 | 7 | ## Library control 8 | 9 | Circuits are saved in circuit libraries. These files can be opened on there own or used as part of another circuit. The top part of the control panel is used for libraries: 10 | 11 | - New: create a new, empty, circuit library 12 | - Load: open a previously created circuit library 13 | - Save: save the currently open circuit library. LSim will ask for a filename the first time a library is saved. 14 | - Add Library: add a reference to an existing library to this library. The circuits in the referenced library can now be used in this circuit library. 15 | 16 | ## Circuit control 17 | 18 | The next part of the control panel handles the circuits in the library. It shows a list of the circuits in the library and a few actions that can be used on them: 19 | 20 | - Up: move the selected circuit up in the list 21 | - Down: move the selected circuit down in the list 22 | - Add: add a new circuit to the library 23 | - Delete: delete the currently selected circuit. If it's not allowed to delete the current circuit, this button will be hidden. 24 | 25 | ## Component list 26 | 27 | The next, and biggest, part of the control window is the component-panel. It lists the basic logic components and any circuits from referenced libraries. From here you can drag them onto the circuit editor to place them in the circuit. A short overview of the built-in, basic components: 28 | 29 | * Gates: 30 | * AND: Output is high when all inputs (2-8) are high; otherwise output is low. 31 | * OR: Output is high when at least one of the inputs (2-8) is high; otherwise output is low. 32 | * NOT: Inverter with 1 input. Output is high when input is low; otherwise output is low. 33 | * NAND: Output is low when all inputs (2-8) are high; otherwise output is high. 34 | * NOR: Output is high when all inputs (2-8) are low; otherwise output is low. 35 | * XOR: Always 2 inputs. Output is high when one input is high and the other is low; otherwise output is low. 36 | * XNOR: Always 2 inputs. Output is low when one input is high and the other is low; otherwise output is high. 37 | * Buffer: Output is the same as the input. Basically a delay or signal amplifier. 38 | * TriState Buffer: Buffer with a control signal. When the control signal is low, the tristate buffer has no output signal (high impedance). When the control signal is high, the output is the same as the input. 39 | * Various components: 40 | * Input: an input-connector allows signal to enter the circuit from an external source. Either from an encompassing circuit or by user control in the top-level circuit. 41 | * Output: an output-connector allows signals to exit the circuit. 42 | * Constant: Basically an input-connector that is always either low or high. 43 | * PullResistor: pulls the value of the connected node towards the specified value (low or high) when the node does not have a value yet. 44 | * Via: provides a connection between equally named via's in a circuit without actually drawing a wire between them. Useful to keep a circuit neat. 45 | * Oscillator: output toggles between high and low at the specified frequency. 46 | * Text: Allows a user to place commentary text in the circuit. 47 | * I/O 48 | * 7-segment LED: a 7 segment LED (with decimal indicator) display. Behaves similarly to it's physical counterparts but the pin-out is slightly more ordered: 49 | * Top row / left to right : top, top-right, bottom-right, bottom 50 | * Bottom row / left to right: bottom-left, top-left, on/off, middle, decimal point 51 | 52 | ## Property editor 53 | 54 | The last part of the control window is the property window. It allows you to change the properties of the selected component or, if no component is selected, of the current circuit. It's content depends on the type of component is selected. 55 | 56 | ## The circuit editor 57 | 58 | The top of the circuit editor allows you to switch between editor-mode and simulation mode. In simulation mode extra options are added to control the simulation. You can single-step through the simulation or let the simulation run at the specified speed. 59 | 60 | The circuit can only be modified when in "Editor"-mode. You can add components to the circuit by dragging them from the control window. Other circuits from the opened library can be added to a circuit by right-clicking and selecting the wanted circuit from the list. 61 | 62 | Select a component by left-clicking inside it's outline. One way to select multiple components is by simultaneously holding down shift on the keyboard when clicking components. You can also select multiple components through area-selection: left-click an empty area of the circuit and drag the mouse while holding down the left mouse button. All components inside the blue area will be selected upon release of the mouse button. 63 | 64 | The little circles on the edge of the components are pins were wires can be connected to pass the signal from an output to an input pin. Hover over a pin and left-click after the yellow circle appears around the pin. Move the mouse cursor to the pin you want to connect to and left-click after the yellow circle appears. You can anchor the wire at a certain point and continue drawing in another direction by left-clicking on an empty spot of the circuit. To terminate the wire without connecting anywhere double-click. 65 | 66 | To move a component select it and drag with the mouse until it is in the right spot. Moving a component disconnects any wires the were connected to it. After moving a component, pins that overlap a wire will automatically connect to that wire. 67 | 68 | To delete components or wires, select them and press the delete key on the keyboard. 69 | 70 | Copying and pasting is also supported by using Control-C and Control-V. -------------------------------------------------------------------------------- /src/model_property.cpp: -------------------------------------------------------------------------------- 1 | // model_property.cpp - Johan Smet - BSD-3-Clause (see LICENSE) 2 | // 3 | // Key/value pair to store extra information about specific components 4 | 5 | #include "model_property.h" 6 | 7 | #include 8 | 9 | namespace { 10 | 11 | inline bool string_to_bool(const std::string &val) { 12 | // tolower should be ok for our use-case. Including ICU in the project for this seems overly complicated. 13 | auto lower = val; 14 | std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower); 15 | 16 | return lower == "true" || lower == "yes"; 17 | } 18 | 19 | const char *VALUE_STRINGS[] = { 20 | "false", 21 | "true", 22 | "undefined", 23 | "error" 24 | }; 25 | 26 | inline lsim::Value string_to_value(const std::string &val) { 27 | // tolower should be ok for our use-case. Including ICU in the project for this seems overly complicated. 28 | auto lower = val; 29 | std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower); 30 | 31 | for (int idx = 0; idx < 4; ++idx) { 32 | if (lower == VALUE_STRINGS[idx]) { 33 | return static_cast(idx); 34 | } 35 | } 36 | 37 | return lsim::VALUE_ERROR; 38 | } 39 | 40 | inline lsim::Value int_to_value(int64_t val) { 41 | if (val >= 0 && val <= 3) { 42 | return static_cast(val); 43 | } 44 | return lsim::VALUE_ERROR; 45 | } 46 | 47 | } // unnamed namespace 48 | 49 | namespace lsim { 50 | 51 | /////////////////////////////////////////////////////////////////////////////// 52 | // 53 | // StringProperty 54 | // 55 | 56 | StringProperty::StringProperty(const char *key, const char *value) : 57 | Property(key), 58 | m_value(value) { 59 | } 60 | 61 | Property::uptr_t StringProperty::clone() const { 62 | return make_property(key(), m_value.c_str()); 63 | } 64 | 65 | std::string StringProperty::value_as_string() const { 66 | return m_value; 67 | } 68 | 69 | int64_t StringProperty::value_as_integer() const { 70 | return std::strtol(m_value.c_str(), nullptr, 0); 71 | } 72 | 73 | bool StringProperty::value_as_boolean() const { 74 | return string_to_bool(m_value); 75 | } 76 | 77 | Value StringProperty::value_as_lsim_value() const { 78 | return string_to_value(m_value); 79 | } 80 | 81 | void StringProperty::value(const char *val) { 82 | m_value = val; 83 | } 84 | 85 | void StringProperty::value(int64_t val) { 86 | m_value = std::to_string(val); 87 | } 88 | 89 | void StringProperty::value(bool val) { 90 | m_value = (val) ? "true" : "false"; 91 | } 92 | 93 | void StringProperty::value(Value val) { 94 | m_value = VALUE_STRINGS[val]; 95 | } 96 | 97 | /////////////////////////////////////////////////////////////////////////////// 98 | // 99 | // IntegerProperty 100 | // 101 | 102 | IntegerProperty::IntegerProperty(const char *key, int64_t value) : 103 | Property(key), 104 | m_value(value) { 105 | } 106 | 107 | Property::uptr_t IntegerProperty::clone() const { 108 | return make_property(key(), m_value); 109 | } 110 | 111 | std::string IntegerProperty::value_as_string() const { 112 | return std::to_string(m_value); 113 | } 114 | 115 | int64_t IntegerProperty::value_as_integer() const { 116 | return m_value; 117 | } 118 | 119 | bool IntegerProperty::value_as_boolean() const { 120 | return m_value != 0; 121 | } 122 | 123 | Value IntegerProperty::value_as_lsim_value() const { 124 | return int_to_value(m_value); 125 | } 126 | 127 | void IntegerProperty::value(const char *val) { 128 | m_value = std::strtoll(val, nullptr, 0); 129 | } 130 | 131 | void IntegerProperty::value(int64_t val) { 132 | m_value = val; 133 | } 134 | 135 | void IntegerProperty::value(bool val) { 136 | m_value = (val) ? 1 : 0; 137 | } 138 | 139 | void IntegerProperty::value(Value val) { 140 | m_value = static_cast (val); 141 | } 142 | 143 | /////////////////////////////////////////////////////////////////////////////// 144 | // 145 | // BoolProperty 146 | // 147 | 148 | BoolProperty::BoolProperty(const char *key, bool value) : 149 | Property(key), 150 | m_value(value) { 151 | } 152 | 153 | Property::uptr_t BoolProperty::clone() const { 154 | return make_property(key(), m_value); 155 | } 156 | 157 | std::string BoolProperty::value_as_string() const { 158 | return (m_value) ? "true" : "false"; 159 | } 160 | 161 | int64_t BoolProperty::value_as_integer() const { 162 | return (m_value) ? 1 : 0; 163 | } 164 | 165 | bool BoolProperty::value_as_boolean() const { 166 | return m_value; 167 | } 168 | 169 | Value BoolProperty::value_as_lsim_value() const { 170 | return m_value ? VALUE_TRUE : VALUE_FALSE; 171 | } 172 | 173 | void BoolProperty::value(const char *val) { 174 | m_value = string_to_bool(val); 175 | } 176 | 177 | void BoolProperty::value(int64_t val) { 178 | m_value = val != 0; 179 | } 180 | 181 | void BoolProperty::value(bool val) { 182 | m_value = val; 183 | } 184 | 185 | void BoolProperty::value(Value val) { 186 | m_value = val == VALUE_TRUE; 187 | } 188 | 189 | /////////////////////////////////////////////////////////////////////////////// 190 | // 191 | // ValueProperty 192 | // 193 | 194 | ValueProperty::ValueProperty(const char *key, Value value) : 195 | Property(key), 196 | m_value(value) { 197 | } 198 | 199 | Property::uptr_t ValueProperty::clone() const { 200 | return make_property(key(), m_value); 201 | } 202 | 203 | std::string ValueProperty::value_as_string() const { 204 | return VALUE_STRINGS[m_value]; 205 | } 206 | 207 | int64_t ValueProperty::value_as_integer() const { 208 | return static_cast(m_value); 209 | } 210 | 211 | bool ValueProperty::value_as_boolean() const { 212 | return m_value == VALUE_TRUE; 213 | } 214 | 215 | Value ValueProperty::value_as_lsim_value() const { 216 | return m_value; 217 | } 218 | 219 | void ValueProperty::value(const char *val) { 220 | m_value = string_to_value(val); 221 | } 222 | 223 | void ValueProperty::value(int64_t val) { 224 | m_value = int_to_value(val); 225 | } 226 | 227 | void ValueProperty::value(bool val) { 228 | m_value = (val) ? VALUE_TRUE : VALUE_FALSE; 229 | } 230 | 231 | void ValueProperty::value(Value val) { 232 | m_value = val; 233 | } 234 | 235 | } // namespace lsim -------------------------------------------------------------------------------- /src/gui/circuit_editor.h: -------------------------------------------------------------------------------- 1 | // component_ui.h - Johan Smet - BSD-3-Clause (see LICENSE) 2 | 3 | #ifndef LSIM_GUI_COMPONENT_UI_H 4 | #define LSIM_GUI_COMPONENT_UI_H 5 | 6 | #include "algebra.h" 7 | #include "sim_types.h" 8 | #include "component_widget.h" 9 | 10 | #include "std_helper.h" 11 | #include 12 | 13 | struct ImDrawList; 14 | 15 | namespace lsim { 16 | 17 | class ModelCircuit; 18 | class ModelWireSegment; 19 | class ModelWire; 20 | class SimCircuit; 21 | 22 | namespace gui { 23 | 24 | class UIContext; 25 | 26 | enum CircuitEditorState { 27 | CS_IDLE = 0, 28 | CS_DRAGGING, 29 | CS_AREA_SELECT, 30 | CS_CREATE_COMPONENT, 31 | CS_CREATE_WIRE, 32 | CS_SIMULATING 33 | }; 34 | 35 | class CircuitEditor { 36 | public: 37 | using uptr_t = unique_ptr; 38 | 39 | public: 40 | CircuitEditor(ModelCircuit *model_circuit); 41 | 42 | // display frame and interaction 43 | void refresh(UIContext *ui_context); 44 | private: 45 | void init_ui_refresh(); 46 | void draw_ui(UIContext *ui_context); 47 | void user_interaction(); 48 | 49 | public: 50 | // properties 51 | Point circuit_dimensions() const; 52 | ModelCircuit *model_circuit() const {return m_model_circuit;} 53 | SimCircuit *sim_circuit() const {return m_sim_circuit;} 54 | 55 | // widget management 56 | ComponentWidget *create_widget_for_component(ModelComponent *component_model); 57 | void remove_widget(ComponentWidget *widget); 58 | void reposition_widget(ComponentWidget *comp, Point pos); 59 | 60 | // circuit modification 61 | void prepare_move_selected_items(); 62 | void move_selected_items(); 63 | void reconnect_selected_items(); 64 | void delete_selected_items(); 65 | void add_wire(); 66 | 67 | void ui_create_component(ModelComponent *component_model); 68 | void ui_embed_circuit(const char *name); 69 | void ui_fix_widget_connections(ComponentWidget *widget); 70 | void ui_fix_wire(ModelWire * wire); 71 | ModelWire* ui_try_merge_wire_segment(ModelWireSegment* segment); 72 | 73 | 74 | // selection 75 | size_t num_selected_items() const {return m_selection.size();} 76 | void clear_selection(); 77 | void select_widget(ComponentWidget *widget); 78 | void deselect_widget(ComponentWidget *widget); 79 | void select_wire_segment(ModelWireSegment *segment); 80 | void deselect_wire_segment(ModelWireSegment *segment); 81 | void select_by_area(Point p0, Point p1); 82 | void toggle_selection(ComponentWidget *widget); 83 | void toggle_selection(ModelWireSegment *segment); 84 | bool is_selected(ComponentWidget *widget); 85 | bool is_selected(ModelWireSegment *segment); 86 | ComponentWidget *selected_widget() const; 87 | 88 | // simulation 89 | void set_simulation_instance(SimCircuit *sim_circuit, bool view_only = false); 90 | bool is_simulating() const; 91 | bool is_view_only_simulation() const {return m_view_only;}; 92 | 93 | // copy & paste 94 | void copy_selected_components(); 95 | void paste_components(); 96 | 97 | private: 98 | void wire_make_connections(ModelWire *wire); 99 | void draw_grid(ImDrawList *draw_list); 100 | void ui_popup_embed_circuit(); 101 | void ui_popup_embed_circuit_open(); 102 | void ui_popup_sub_circuit(UIContext *ui_context); 103 | void ui_popup_sub_circuit_open(); 104 | void ui_popup_edit_segment(); 105 | void ui_popup_edit_segment_open(); 106 | 107 | private: 108 | struct PointHash { 109 | size_t operator() (const Point &p) const; 110 | }; 111 | 112 | using component_container_t = std::vector >; 113 | using widget_container_t = std::vector >; 114 | using point_container_t = std::vector; 115 | using point_pin_lut_t = std::unordered_map; 116 | 117 | struct WireEndPoint { 118 | Point m_position; 119 | pin_id_t m_pin; 120 | ModelWire * m_wire; 121 | }; 122 | 123 | struct SelectedItem { 124 | ComponentWidget *m_widget; 125 | ModelWireSegment *m_segment; 126 | }; 127 | using selection_container_t = std::vector; 128 | 129 | private: 130 | ModelCircuit * m_model_circuit; // the circuit being shown/edited 131 | 132 | // circuit elements 133 | widget_container_t m_widgets; // widgets for the components 134 | point_pin_lut_t m_point_pin_lut; // Point -> pin lookup 135 | 136 | // selection 137 | selection_container_t m_selection; // selected items 138 | Point m_area_sel_a; // start point of area selection 139 | 140 | // copy & paste (fixme: move to higher level to allow copy&paste between circuits) 141 | component_container_t m_copy_components; 142 | Point m_copy_center; 143 | 144 | // editor state 145 | CircuitEditorState m_state; // current state of the editor state machine 146 | ComponentWidget * m_hovered_widget; // currently hovered widget 147 | pin_id_t m_hovered_pin; // currently hovered pin 148 | ModelWire * m_hovered_wire; // currently hovered wire 149 | Point m_scroll_delta; // distance the origin of circuit has been scrolled from origin of editor window 150 | Point m_screen_offset; // translation from circuit space to screen space 151 | 152 | Point m_mouse_grid_point; // grid point nearest to the mouse cursor 153 | Point m_dragging_last_point; // last point items were moved to while dragging 154 | 155 | // wire creation 156 | WireEndPoint m_wire_start; // starting anchor point of the wire 157 | WireEndPoint m_wire_end; // ending anchor point of the wire 158 | Point m_segment_start; // start point of current segment 159 | point_container_t m_line_anchors; // all anchor points of the line being created 160 | 161 | // simulation specific variables 162 | SimCircuit * m_sim_circuit; // simulation circuit (nullptr == not simulating atm) 163 | bool m_view_only; // view only simulation (user cannot change connector values) 164 | ComponentWidget * m_popup_component; // drill-down target component 165 | 166 | // drawing properties 167 | bool m_show_grid; // draw grid? 168 | }; 169 | 170 | 171 | class CircuitEditorFactory { 172 | public: 173 | using materialize_func_t = std::function; 174 | public: 175 | static void register_materialize_func(ComponentType type, materialize_func_t func); 176 | static CircuitEditor::uptr_t create_circuit(ModelCircuit *circuit); 177 | static void materialize_component(ComponentWidget *ui_component); 178 | static void rematerialize_component(CircuitEditor *circuit, ComponentWidget *ui_component); 179 | 180 | private: 181 | using materialize_func_map_t = std::unordered_map; 182 | 183 | private: 184 | static materialize_func_map_t m_materialize_funcs; 185 | 186 | }; 187 | 188 | } // namespace lsim::gui 189 | 190 | } // namespace lsim 191 | 192 | #endif // LSIM_GUI_COMPONENT_UI_H -------------------------------------------------------------------------------- /tests/test_extra.cpp: -------------------------------------------------------------------------------- 1 | #include "catch.hpp" 2 | #include "lsim_context.h" 3 | #include "sim_circuit.h" 4 | 5 | using namespace lsim; 6 | 7 | TEST_CASE("PullResistor", "[extra]") { 8 | 9 | LSimContext lsim_context; 10 | auto sim = lsim_context.sim(); 11 | 12 | auto circuit_desc = lsim_context.create_user_circuit("main"); 13 | REQUIRE(circuit_desc); 14 | 15 | auto in = circuit_desc->add_connector_in("in", 1); 16 | REQUIRE(in); 17 | 18 | auto out = circuit_desc->add_connector_out("out", 1); 19 | REQUIRE(out); 20 | 21 | auto pull = circuit_desc->add_pull_resistor(VALUE_TRUE); 22 | REQUIRE(pull); 23 | 24 | circuit_desc->connect(in->pin_id(0), out->pin_id(0)); 25 | circuit_desc->connect(pull->pin_id(0), out->pin_id(0)); 26 | 27 | Value truth_table[][2] = { 28 | // in out 29 | {VALUE_FALSE, VALUE_FALSE}, 30 | {VALUE_TRUE, VALUE_TRUE}, 31 | {VALUE_UNDEFINED, VALUE_TRUE} 32 | }; 33 | 34 | auto circuit = circuit_desc->instantiate(sim); 35 | REQUIRE(circuit); 36 | 37 | sim->init(); 38 | 39 | for (const auto &test : truth_table) { 40 | circuit->write_pin(in->pin_id(0), test[0]); 41 | sim->run_until_stable(5); 42 | REQUIRE(circuit->read_pin(out->pin_id(0)) == test[1]); 43 | } 44 | } 45 | 46 | TEST_CASE("Via", "[extra]") { 47 | 48 | LSimContext lsim_context; 49 | auto sim = lsim_context.sim(); 50 | 51 | auto circuit_desc = lsim_context.create_user_circuit("main"); 52 | REQUIRE(circuit_desc); 53 | 54 | auto in = circuit_desc->add_connector_in("in", 3); 55 | REQUIRE(in); 56 | 57 | auto via_1 = circuit_desc->add_via("via", 2); 58 | REQUIRE(via_1); 59 | auto via_2 = circuit_desc->add_via("via", 2); 60 | REQUIRE(via_2); 61 | auto via_3 = circuit_desc->add_via("via", 2); 62 | REQUIRE(via_3); 63 | auto via_a = circuit_desc->add_via("via2", 1); 64 | REQUIRE(via_a); 65 | auto via_b = circuit_desc->add_via("via2", 1); 66 | REQUIRE(via_b); 67 | 68 | auto out_a = circuit_desc->add_connector_out("out_a", 2); 69 | REQUIRE(out_a); 70 | auto out_b = circuit_desc->add_connector_out("out_b", 2); 71 | REQUIRE(out_b); 72 | auto out_c = circuit_desc->add_connector_out("out_c", 1); 73 | REQUIRE(out_c); 74 | 75 | auto not_1 = circuit_desc->add_not_gate(); 76 | REQUIRE(not_1); 77 | auto not_2 = circuit_desc->add_not_gate(); 78 | REQUIRE(not_2); 79 | 80 | circuit_desc->connect(in->pin_id(0), via_1->pin_id(0)); 81 | circuit_desc->connect(in->pin_id(1), via_1->pin_id(1)); 82 | circuit_desc->connect(in->pin_id(2), via_a->pin_id(0)); 83 | 84 | circuit_desc->connect(via_2->pin_id(0), out_a->pin_id(0)); 85 | circuit_desc->connect(via_2->pin_id(1), out_a->pin_id(1)); 86 | 87 | circuit_desc->connect(via_3->pin_id(0), not_1->input_pin_id(0)); 88 | circuit_desc->connect(via_3->pin_id(1), not_2->input_pin_id(0)); 89 | 90 | circuit_desc->connect(not_1->output_pin_id(0), out_b->pin_id(0)); 91 | circuit_desc->connect(not_2->output_pin_id(0), out_b->pin_id(1)); 92 | 93 | circuit_desc->connect(via_b->pin_id(0), out_c->pin_id(0)); 94 | 95 | Value truth_table[][8] = { 96 | // in[0] in[1] in[2] out_a[0] out_a[1] out_b[0] out_b[1] 97 | {VALUE_FALSE, VALUE_FALSE, VALUE_FALSE, VALUE_FALSE, VALUE_FALSE, VALUE_TRUE, VALUE_TRUE, VALUE_FALSE}, 98 | {VALUE_TRUE, VALUE_FALSE, VALUE_FALSE, VALUE_TRUE, VALUE_FALSE, VALUE_FALSE, VALUE_TRUE, VALUE_FALSE}, 99 | {VALUE_FALSE, VALUE_TRUE, VALUE_FALSE, VALUE_FALSE, VALUE_TRUE, VALUE_TRUE, VALUE_FALSE, VALUE_FALSE}, 100 | {VALUE_TRUE, VALUE_TRUE, VALUE_FALSE, VALUE_TRUE, VALUE_TRUE, VALUE_FALSE, VALUE_FALSE, VALUE_FALSE}, 101 | {VALUE_FALSE, VALUE_FALSE, VALUE_TRUE, VALUE_FALSE, VALUE_FALSE, VALUE_TRUE, VALUE_TRUE, VALUE_TRUE}, 102 | {VALUE_TRUE, VALUE_FALSE, VALUE_TRUE, VALUE_TRUE, VALUE_FALSE, VALUE_FALSE, VALUE_TRUE, VALUE_TRUE}, 103 | {VALUE_FALSE, VALUE_TRUE, VALUE_TRUE, VALUE_FALSE, VALUE_TRUE, VALUE_TRUE, VALUE_FALSE, VALUE_TRUE}, 104 | {VALUE_TRUE, VALUE_TRUE, VALUE_TRUE, VALUE_TRUE, VALUE_TRUE, VALUE_FALSE, VALUE_FALSE, VALUE_TRUE} 105 | }; 106 | 107 | auto circuit = circuit_desc->instantiate(sim); 108 | REQUIRE(circuit); 109 | 110 | sim->init(); 111 | 112 | for (const auto &test : truth_table) { 113 | circuit->write_pin(in->pin_id(0), test[0]); 114 | circuit->write_pin(in->pin_id(1), test[1]); 115 | circuit->write_pin(in->pin_id(2), test[2]); 116 | sim->run_until_stable(5); 117 | REQUIRE(circuit->read_pin(out_a->pin_id(0)) == test[3]); 118 | REQUIRE(circuit->read_pin(out_a->pin_id(1)) == test[4]); 119 | REQUIRE(circuit->read_pin(out_b->pin_id(0)) == test[5]); 120 | REQUIRE(circuit->read_pin(out_b->pin_id(1)) == test[6]); 121 | REQUIRE(circuit->read_pin(out_c->pin_id(0)) == test[7]); 122 | } 123 | } 124 | 125 | TEST_CASE("Oscillator", "[extra]") { 126 | 127 | LSimContext lsim_context; 128 | auto sim = lsim_context.sim(); 129 | 130 | auto circuit_desc = lsim_context.create_user_circuit("main"); 131 | REQUIRE(circuit_desc); 132 | 133 | auto clock = circuit_desc->add_oscillator(3, 2); 134 | REQUIRE(clock); 135 | 136 | auto out = circuit_desc->add_connector_out("out", 1); 137 | REQUIRE(out); 138 | 139 | circuit_desc->connect(clock->output_pin_id(0), out->pin_id(0)); 140 | 141 | auto circuit = circuit_desc->instantiate(sim); 142 | REQUIRE(circuit); 143 | 144 | sim->init(); 145 | 146 | for (int i = 0; i < 3; ++i) { 147 | REQUIRE(circuit->read_pin(out->pin_id(0)) == VALUE_FALSE); 148 | sim->step(); 149 | REQUIRE(circuit->read_pin(out->pin_id(0)) == VALUE_FALSE); 150 | sim->step(); 151 | REQUIRE(circuit->read_pin(out->pin_id(0)) == VALUE_FALSE); 152 | sim->step(); 153 | REQUIRE(circuit->read_pin(out->pin_id(0)) == VALUE_TRUE); 154 | sim->step(); 155 | REQUIRE(circuit->read_pin(out->pin_id(0)) == VALUE_TRUE); 156 | sim->step(); 157 | } 158 | } 159 | 160 | TEST_CASE("Oscillator Long", "[extra]") { 161 | 162 | LSimContext lsim_context; 163 | auto sim = lsim_context.sim(); 164 | 165 | auto circuit_desc = lsim_context.create_user_circuit("main"); 166 | REQUIRE(circuit_desc); 167 | 168 | auto clock = circuit_desc->add_oscillator(40, 40); 169 | REQUIRE(clock); 170 | 171 | auto out = circuit_desc->add_connector_out("out", 1); 172 | REQUIRE(out); 173 | 174 | circuit_desc->connect(clock->output_pin_id(0), out->pin_id(0)); 175 | 176 | auto circuit = circuit_desc->instantiate(sim); 177 | REQUIRE(circuit); 178 | 179 | sim->init(); 180 | 181 | for (int i = 0; i < 20; ++i) { 182 | for (int j = 0; j < 40; ++j) { 183 | REQUIRE(circuit->read_pin(out->pin_id(0)) == VALUE_FALSE); 184 | sim->step(); 185 | } 186 | 187 | for (int j = 0; j < 40; ++j) { 188 | REQUIRE(circuit->read_pin(out->pin_id(0)) == VALUE_TRUE); 189 | sim->step(); 190 | } 191 | } 192 | } -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | 3 | project(lsim) 4 | 5 | # options 6 | option(PYTHON_BINDINGS "Enable Python bindings (pybind11)" OFF) 7 | 8 | # force C++14 for all targets 9 | set(CMAKE_CXX_STANDARD 14) 10 | 11 | # platform detection 12 | string(TOUPPER ${CMAKE_SYSTEM_NAME} PLATFORM_NAME) 13 | string(CONCAT PLATFORM_DEF "PLATFORM_" ${PLATFORM_NAME}) 14 | 15 | # 16 | # helper functions 17 | # 18 | 19 | function (lsim_source_group p_target p_dir) 20 | get_target_property(SOURCES_LIST ${p_target} SOURCES) 21 | source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR}/${p_dir} FILES ${SOURCES_LIST}) 22 | endfunction() 23 | 24 | 25 | # 26 | # external dependencies 27 | # 28 | 29 | # pugixml 30 | add_subdirectory("libs/pugixml") 31 | set_property(TARGET pugixml PROPERTY POSITION_INDEPENDENT_CODE ON) 32 | set (PUGIXML_INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/libs/pugixml/src) 33 | 34 | # cute 35 | set(CUTE_TARGET cute) 36 | set (CUTE_INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/libs/cute) 37 | add_library(${CUTE_TARGET} STATIC) 38 | target_sources(${CUTE_TARGET} PRIVATE 39 | libs/cute/cute_files.cpp 40 | libs/cute/cute_files.h 41 | ) 42 | 43 | # SDL2 / OpenGL 44 | if (NOT EMSCRIPTEN) 45 | find_package(SDL2 REQUIRED) 46 | if (TARGET SDL2::SDL2) 47 | set(SDL2_LIBRARIES SDL2::SDL2) 48 | endif() 49 | find_package(OpenGL REQUIRED) 50 | endif() 51 | 52 | # 53 | # simulator library 54 | # 55 | 56 | set (LIB_TARGET lsim) 57 | 58 | add_library(${LIB_TARGET} STATIC) 59 | target_sources(${LIB_TARGET} 60 | PRIVATE 61 | src/algebra.cpp 62 | src/algebra.h 63 | src/error.cpp 64 | src/error.h 65 | src/load_logisim.cpp 66 | src/load_logisim.h 67 | src/lsim_context.cpp 68 | src/lsim_context.h 69 | src/model_circuit.cpp 70 | src/model_circuit.h 71 | src/model_circuit_library.cpp 72 | src/model_circuit_library.h 73 | src/model_component.cpp 74 | src/model_component.h 75 | src/model_wire.cpp 76 | src/model_wire.h 77 | src/model_property.cpp 78 | src/model_property.h 79 | src/serialize.cpp 80 | src/serialize.h 81 | src/simulator.cpp 82 | src/simulator.h 83 | src/sim_component.cpp 84 | src/sim_component.h 85 | src/sim_circuit.cpp 86 | src/sim_circuit.h 87 | src/sim_functions.cpp 88 | src/sim_functions.h 89 | src/sim_gates.cpp 90 | src/sim_various.cpp 91 | src/sim_types.h 92 | src/std_helper.h 93 | ) 94 | target_include_directories(${LIB_TARGET} PRIVATE ${PUGIXML_INCLUDE}) 95 | target_compile_definitions(${LIB_TARGET} PRIVATE ${PLATFORM_DEF}) 96 | target_link_libraries(${LIB_TARGET} PUBLIC pugixml) 97 | set_property(TARGET ${LIB_TARGET} PROPERTY POSITION_INDEPENDENT_CODE ON) 98 | 99 | lsim_source_group(${LIB_TARGET} src) 100 | 101 | # 102 | # python bindings 103 | # 104 | 105 | if (PYTHON_BINDINGS) 106 | set (PYTHON_TARGET lsimpy) 107 | 108 | add_subdirectory(libs/pybind11) 109 | pybind11_add_module(${PYTHON_TARGET} src/python.cpp) 110 | target_include_directories(${PYTHON_TARGET} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/libs ${PUGIXML_INCLUDE}) 111 | target_link_libraries(${PYTHON_TARGET} PRIVATE ${LIB_TARGET}) 112 | endif() 113 | 114 | # 115 | # MAIN executable 116 | # 117 | 118 | set(APP_TARGET lsim_gui) 119 | 120 | add_executable(${APP_TARGET} WIN32) 121 | target_sources(${APP_TARGET} 122 | PRIVATE 123 | src/gui/imgui/imgui_draw.cpp 124 | src/gui/imgui/imgui_internal.h 125 | src/gui/imgui/imgui.cpp 126 | src/gui/imgui/imgui.h 127 | src/gui/imgui/imgui_impl_sdl.cpp 128 | src/gui/imgui/imgui_impl_sdl.h 129 | src/gui/imgui/imgui_impl_opengl3.cpp 130 | src/gui/imgui/imgui_impl_opengl3.h 131 | src/gui/imgui/imgui_widgets.cpp 132 | src/gui/imgui/stb_rect_pack.h 133 | src/gui/imgui/stb_textedit.h 134 | src/gui/imgui/stb_truetype.h 135 | src/gui/circuit_editor.cpp 136 | src/gui/circuit_editor.h 137 | src/gui/component_draw.cpp 138 | src/gui/component_draw.h 139 | src/gui/component_icon.cpp 140 | src/gui/component_icon.h 141 | src/gui/component_widget.cpp 142 | src/gui/component_widget.h 143 | src/gui/configuration.h 144 | src/gui/imgui_ex.cpp 145 | src/gui/imgui_ex.h 146 | src/gui/shapes.h 147 | src/gui/ui_context.cpp 148 | src/gui/ui_context.h 149 | src/gui/ui_panel_component.cpp 150 | src/gui/ui_panel_circuit.cpp 151 | src/gui/ui_panel_property.cpp 152 | src/gui/ui_panel_library.cpp 153 | src/gui/ui_popup_files.cpp 154 | src/gui/ui_popup_files.h 155 | src/gui/ui_window_main.cpp 156 | src/gui/ui_window_main.h 157 | ) 158 | 159 | target_link_libraries(${APP_TARGET} PRIVATE ${LIB_TARGET} ${CUTE_TARGET} ${CMAKE_DL_LIBS}) 160 | target_include_directories(${APP_TARGET} PRIVATE src ${CUTE_INCLUDE}) 161 | target_compile_definitions(${APP_TARGET} PRIVATE ${PLATFORM_DEF}) 162 | 163 | if (NOT EMSCRIPTEN) 164 | target_sources(${APP_TARGET} PRIVATE 165 | src/gui/gl3w/GL/gl3w.c 166 | src/gui/gl3w/GL/gl3w.h 167 | src/gui/gl3w/GL/glcorearb.h 168 | src/gui/main.cpp 169 | ) 170 | target_include_directories(${APP_TARGET} PRIVATE ${SDL2_INCLUDE_DIRS} ${OPENGL_INCLUDE_DIR} src/gui/gl3w) 171 | target_link_libraries(${APP_TARGET} PRIVATE ${SDL2_LIBRARIES} ${OPENGL_LIBRARIES}) 172 | 173 | if (APPLE) 174 | target_link_libraries(${APP_TARGET} PRIVATE "-framework CoreFoundation") 175 | endif (APPLE) 176 | else() 177 | target_sources(${APP_TARGET} PRIVATE 178 | src/gui/main_emscripten.cpp 179 | ) 180 | 181 | target_compile_options(${APP_TARGET} PRIVATE -s USE_SDL=2) 182 | target_link_libraries(${APP_TARGET} PRIVATE "-s USE_SDL=2") 183 | target_link_libraries(${APP_TARGET} PRIVATE 184 | "-s WASM=1" 185 | "-s ALLOW_MEMORY_GROWTH=1" 186 | "--no-heap-copy" 187 | ) 188 | target_link_libraries(${APP_TARGET} PRIVATE 189 | "--shell-file ${CMAKE_CURRENT_SOURCE_DIR}/src/gui/shell_minimal.html" 190 | "-o lsim.html" 191 | ) 192 | target_link_libraries(${APP_TARGET} PRIVATE 193 | "--preload-file ${CMAKE_CURRENT_SOURCE_DIR}/examples@/examples --exclude-file *.circ" 194 | ) 195 | endif() 196 | 197 | lsim_source_group(${APP_TARGET} src/gui) 198 | 199 | 200 | # 201 | # speedtest 202 | # 203 | 204 | set(SPEED_TARGET speedtest) 205 | 206 | add_executable(${SPEED_TARGET}) 207 | target_sources(${SPEED_TARGET} PRIVATE src/tools/speedtest/speedtest_main.cpp) 208 | 209 | target_include_directories(${SPEED_TARGET} PRIVATE src) 210 | target_compile_definitions(${SPEED_TARGET} PRIVATE ${PLATFORM_DEF}) 211 | target_link_libraries(${SPEED_TARGET} PRIVATE ${LIB_TARGET} ${CMAKE_DL_LIBS}) 212 | 213 | # 214 | # Unit tests 215 | # 216 | 217 | enable_testing() 218 | 219 | add_executable(test_runner) 220 | target_sources(test_runner 221 | PRIVATE 222 | tests/catch.hpp 223 | tests/test_main.cpp 224 | tests/test_algebra.cpp 225 | tests/test_gate.cpp 226 | tests/test_extra.cpp 227 | tests/test_circuit.cpp 228 | tests/test_logisim.cpp 229 | ) 230 | target_include_directories(test_runner PRIVATE src) 231 | target_link_libraries(test_runner PRIVATE ${LIB_TARGET}) 232 | add_test(NAME unittests COMMAND test_runner) 233 | 234 | if (NOT EMSCRIPTEN) 235 | set (TEST_CMD ${CMAKE_CTEST_COMMAND} -C $ --output-on-failures) 236 | else() 237 | set (TEST_CMD node test_runner) 238 | endif() 239 | 240 | add_custom_command( 241 | TARGET test_runner 242 | COMMENT "Run tests" 243 | POST_BUILD 244 | WORKING_DIRECTORY ${CMAKE_BINARY_DIR} 245 | COMMAND ${TEST_CMD} 246 | ) 247 | -------------------------------------------------------------------------------- /src/gui/main_emscripten.cpp: -------------------------------------------------------------------------------- 1 | // dear imgui: standalone example application for Emscripten, using SDL2 + OpenGL3 2 | // This is mostly the same code as the SDL2 + OpenGL3 example, simply with the modifications needed to run on Emscripten. 3 | // It is possible to combine both code into a single source file that will compile properly on Desktop and using Emscripten. 4 | // See https://github.com/ocornut/imgui/pull/2492 as an example on how to do just that. 5 | // 6 | // If you are new to dear imgui, see examples/README.txt and documentation at the top of imgui.cpp. 7 | // (Emscripten is a C++-to-javascript compiler, used to publish executables for the web. See https://emscripten.org/) 8 | 9 | #include "imgui/imgui.h" 10 | #include "imgui/imgui_impl_sdl.h" 11 | #include "imgui/imgui_impl_opengl3.h" 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "ui_window_main.h" 18 | 19 | // Emscripten requires to have full control over the main loop. We're going to store our SDL book-keeping variables globally. 20 | // Having a single function that acts as a loop prevents us to store state in the stack of said function. So we need some location for this. 21 | SDL_Window* g_Window = NULL; 22 | SDL_GLContext g_GLContext = NULL; 23 | 24 | // For clarity, our main loop code is declared at the end. 25 | void main_loop(void*); 26 | 27 | int main(int, char**) 28 | { 29 | // Setup SDL 30 | if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) != 0) 31 | { 32 | printf("Error: %s\n", SDL_GetError()); 33 | return -1; 34 | } 35 | 36 | // For the browser using Emscripten, we are going to use WebGL1 with GL ES2. See the Makefile. for requirement details. 37 | // It is very likely the generated file won't work in many browsers. Firefox is the only sure bet, but I have successfully 38 | // run this code on Chrome for Android for example. 39 | const char* glsl_version = "#version 100"; 40 | //const char* glsl_version = "#version 300 es"; 41 | SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0); 42 | SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); 43 | SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); 44 | SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); 45 | 46 | // Create window with graphics context 47 | SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); 48 | SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); 49 | SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); 50 | SDL_DisplayMode current; 51 | SDL_GetCurrentDisplayMode(0, ¤t); 52 | SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); 53 | g_Window = SDL_CreateWindow("LSim - A digital logic simulator", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, window_flags); 54 | g_GLContext = SDL_GL_CreateContext(g_Window); 55 | if (!g_GLContext) 56 | { 57 | fprintf(stderr, "Failed to initialize WebGL context!\n"); 58 | return 1; 59 | } 60 | SDL_GL_SetSwapInterval(1); // Enable vsync 61 | 62 | // Setup Dear ImGui context 63 | IMGUI_CHECKVERSION(); 64 | ImGui::CreateContext(); 65 | ImGuiIO& io = ImGui::GetIO(); (void)io; 66 | //io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls 67 | //io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls 68 | 69 | // For an Emscripten build we are disabling file-system access, so let's not attempt to do a fopen() of the imgui.ini file. 70 | // You may manually call LoadIniSettingsFromMemory() to load settings from your own storage. 71 | io.IniFilename = NULL; 72 | 73 | // Setup Dear ImGui style 74 | //ImGui::StyleColorsDark(); 75 | ImGui::StyleColorsClassic(); 76 | 77 | // Setup Platform/Renderer bindings 78 | ImGui_ImplSDL2_InitForOpenGL(g_Window, g_GLContext); 79 | ImGui_ImplOpenGL3_Init(glsl_version); 80 | 81 | // Load Fonts 82 | // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. 83 | // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple. 84 | // - If the file cannot be loaded, the function will return NULL. Please handle those errors in your application (e.g. use an assertion, or display an error and quit). 85 | // - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call. 86 | // - Read 'misc/fonts/README.txt' for more instructions and details. 87 | // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! 88 | //io.Fonts->AddFontDefault(); 89 | //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); 90 | //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); 91 | //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); 92 | //io.Fonts->AddFontFromFileTTF("../../misc/fonts/ProggyTiny.ttf", 10.0f); 93 | //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, NULL, io.Fonts->GetGlyphRangesJapanese()); 94 | //IM_ASSERT(font != NULL); 95 | 96 | // create the LSim context here, it should last the lifetime of the application 97 | lsim::gui::main_window_setup("examples/compare.lsim"); 98 | 99 | // This function call won't return, and will engage in an infinite loop, processing events from the browser, and dispatching them. 100 | emscripten_set_main_loop_arg(main_loop, NULL, 0, true); 101 | } 102 | 103 | void main_loop(void* arg) 104 | { 105 | ImGuiIO& io = ImGui::GetIO(); 106 | IM_UNUSED(arg); // We can pass this argument as the second parameter of emscripten_set_main_loop_arg(), but we don't use that. 107 | 108 | // Our state (make them static = more or less global) as a convenience to keep the example terse. 109 | static bool show_demo_window = true; 110 | static bool show_another_window = false; 111 | static ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); 112 | 113 | // Poll and handle events (inputs, window resize, etc.) 114 | // You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs. 115 | // - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application. 116 | // - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application. 117 | // Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags. 118 | SDL_Event event; 119 | while (SDL_PollEvent(&event)) 120 | { 121 | ImGui_ImplSDL2_ProcessEvent(&event); 122 | // Capture events here, based on io.WantCaptureMouse and io.WantCaptureKeyboard 123 | } 124 | 125 | // Start the Dear ImGui frame 126 | ImGui_ImplOpenGL3_NewFrame(); 127 | ImGui_ImplSDL2_NewFrame(g_Window); 128 | ImGui::NewFrame(); 129 | 130 | // GUI 131 | lsim::gui::main_window_update(); 132 | 133 | // Rendering 134 | ImGui::Render(); 135 | SDL_GL_MakeCurrent(g_Window, g_GLContext); 136 | glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y); 137 | glClearColor(clear_color.x, clear_color.y, clear_color.z, clear_color.w); 138 | glClear(GL_COLOR_BUFFER_BIT); 139 | ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); 140 | SDL_GL_SwapWindow(g_Window); 141 | } 142 | --------------------------------------------------------------------------------