From bfeeac6e4889d1e9a1083b3c7efc59652981b168 Mon Sep 17 00:00:00 2001 From: Trygve Laugstøl Date: Mon, 1 Aug 2016 08:20:23 +0200 Subject: o Moving the generation logic to Python, embedding a Python interpreter with the help of pybind11. o Adding install configuration to CMake to make it easier to reuse the project later on. o Splitting out the examples into its own project to make it easier to test the whole installation setup and python/template loading. o Trying to reorganize the code a bit, not very much better yet. --- .gitmodules | 5 +- CMakeLists.txt | 21 +- KicadNetLexer.g4 | 38 -- KicadNetParser.g4 | 89 ----- cli/CMakeLists.txt | 29 ++ cli/generate-header.cpp | 466 ++++++++++++++++++++++++ cmake/KicadUtilsConfig.cmake | 2 + cmake/kicad_generate_header.cmake | 57 +++ configure | 51 +++ core/CMakeLists.txt | 22 ++ core/KicadNetLexer.g4 | 38 ++ core/KicadNetParser.g4 | 89 +++++ core/include-priv/trygvis/antlr.h | 67 ++++ core/include-priv/trygvis/string_utils.h | 23 ++ core/include/trygvis/kicad.h | 102 ++++++ core/kicad.cpp | 194 ++++++++++ examples/CMakeLists.txt | 9 + examples/arduino-led/CMakeLists.txt | 2 + examples/intel-quark-d2000/CMakeLists.txt | 7 +- examples/intel-quark-d2000/intel-quark-d2000.py | 2 + generate-header.cpp | 311 ---------------- include-priv/trygvis/antlr.h | 67 ---- include-priv/trygvis/string_utils.h | 23 -- include/trygvis/kicad.h | 99 ----- kicad.cmake | 43 --- kicad.cpp | 194 ---------- python/CMakeLists.txt | 13 + python/GenerateHeaderPy.cpp | 40 ++ python/include/trygvis/kicad/GenerateHeaderPy.h | 45 +++ python/include/trygvis/kicad/python.h | 3 + python/kicad_utils_py.cpp | 38 ++ python/pybind11 | 1 + template/intel-quark-d2000.py | 69 ++++ 33 files changed, 1379 insertions(+), 880 deletions(-) delete mode 100644 KicadNetLexer.g4 delete mode 100644 KicadNetParser.g4 create mode 100644 cli/CMakeLists.txt create mode 100644 cli/generate-header.cpp create mode 100644 cmake/KicadUtilsConfig.cmake create mode 100644 cmake/kicad_generate_header.cmake create mode 100755 configure create mode 100644 core/CMakeLists.txt create mode 100644 core/KicadNetLexer.g4 create mode 100644 core/KicadNetParser.g4 create mode 100644 core/include-priv/trygvis/antlr.h create mode 100644 core/include-priv/trygvis/string_utils.h create mode 100644 core/include/trygvis/kicad.h create mode 100644 core/kicad.cpp create mode 100644 examples/intel-quark-d2000/intel-quark-d2000.py delete mode 100644 generate-header.cpp delete mode 100644 include-priv/trygvis/antlr.h delete mode 100644 include-priv/trygvis/string_utils.h delete mode 100644 include/trygvis/kicad.h delete mode 100644 kicad.cmake delete mode 100644 kicad.cpp create mode 100644 python/CMakeLists.txt create mode 100644 python/GenerateHeaderPy.cpp create mode 100644 python/include/trygvis/kicad/GenerateHeaderPy.h create mode 100644 python/include/trygvis/kicad/python.h create mode 100644 python/kicad_utils_py.cpp create mode 160000 python/pybind11 create mode 100644 template/intel-quark-d2000.py diff --git a/.gitmodules b/.gitmodules index a84f300..83b6a4d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,7 @@ [submodule "pybind11"] - path = pybind11 + path = python/pybind11 url = https://github.com/pybind/pybind11.git branch = master +[submodule "python/pybind11"] + path = python/pybind11 + url = https://github.com/pybind/pybind11 diff --git a/CMakeLists.txt b/CMakeLists.txt index 926f7c0..e70bce4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,18 +1,13 @@ cmake_minimum_required(VERSION 3.5) project(kicad_utils) -find_package(Antlr4) -antlr4_add_target(TARGET KicadNet LEXER KicadNetLexer.g4 PARSER KicadNetParser.g4 STATIC) +add_compile_options(--std=c++14) +add_compile_options(-Wall) +add_compile_options(-Wextra) -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14") +add_subdirectory(core) +add_subdirectory(python) +add_subdirectory(cli) +#add_subdirectory(examples) -set(SOURCE_FILES generate-header.cpp kicad.cpp include/trygvis/kicad.h include-priv/trygvis/antlr.h include-priv/trygvis/string_utils.h) -add_executable(generate-header ${SOURCE_FILES}) -target_include_directories(generate-header PUBLIC include PRIVATE include-priv) -target_link_libraries(generate-header KicadNet Antlr4::antlr4_shared) -target_compile_options(generate-header PUBLIC --std=c++14) -target_compile_options(generate-header PUBLIC -Wall -Wextra) - -include(kicad.cmake) -add_custom_target(all_examples) -add_subdirectory(examples) +message(STATUS "The examples are not built as they depend on a full installation of kicad-utils. Do a make install first and then build the examples") diff --git a/KicadNetLexer.g4 b/KicadNetLexer.g4 deleted file mode 100644 index e10f7e7..0000000 --- a/KicadNetLexer.g4 +++ /dev/null @@ -1,38 +0,0 @@ -lexer grammar KicadNetLexer; - -LPAREN: '('; -RPAREN: ')'; - -QUOTE: '"'; - -CODE: 'code'; -COMP: 'comp'; -FIELD: 'field'; -LIBSOURCE: 'libsource'; -LIBPART: 'libpart'; -LIB: 'lib'; -NAME: 'name'; -NET: 'net'; -NODE: 'node'; -NUM: 'num'; -PART: 'part'; -PIN: 'pin'; -REF: 'ref'; -TYPE: 'type'; -VALUE: 'value'; - -STRING: '"' ~["]* '"'; - -INTEGER: [0-9]+; - -ID - : [/+~\_\-\.\*\?/a-zA-Z0-9]+ - ; - -BlockComment - : '/*' .*? '*/' -> skip - ; - -WS - : [ \t\r\n]+ -> skip - ; diff --git a/KicadNetParser.g4 b/KicadNetParser.g4 deleted file mode 100644 index 3971111..0000000 --- a/KicadNetParser.g4 +++ /dev/null @@ -1,89 +0,0 @@ -parser grammar KicadNetParser; - -options { - tokenVocab = KicadNetLexer; -} - -file: - form+ - ; - -form: - name #formName - | component #formComponent - | field #formField - | net #formNet - | node #formNode - | pinDecl #formDecl - | ref #formRef - | value #formValue - | libpart #formLibpart - | keyValue #formKeyValue - | string #formString - ; - -code: - '(' 'code' INTEGER ')' - ; - -component: - '(' 'comp' ref value libsource keyValue* ')' - ; - -field: - '(' 'field' name string ')' - ; - -name: - '(' 'name' string ')' - ; - -net: - '(' 'net' code name node+ ')' - ; - -node: - '(' 'node' ref pinRef ')' - ; - -pinRef: - '(' 'pin' INTEGER ')' - ; - -pinDecl: - '(' 'pin' '(' 'num' INTEGER ')' '(' 'name' string ')' '(' 'type' string ')' ')' - ; - -ref: - '(' 'ref' string ')' - ; - -value: - '(' 'value' string ')' - ; - -lib: - '(' 'lib' string ')' - ; - -part: - '(' 'part' string ')' - ; - -libpart: - '(' 'libpart' lib part keyValue* ')' - ; - -libsource: - '(' 'libsource' lib part ')' - ; - -keyValue: - '(' string form* ')' - ; - -string: - ID #stringId - | INTEGER #stringInt - | STRING #stringText - ; diff --git a/cli/CMakeLists.txt b/cli/CMakeLists.txt new file mode 100644 index 0000000..f3dd326 --- /dev/null +++ b/cli/CMakeLists.txt @@ -0,0 +1,29 @@ +set(SOURCE_FILES generate-header.cpp) +add_executable(generate-header ${SOURCE_FILES}) + +target_link_libraries(generate-header PUBLIC kicad-utils-core) + +get_target_property(kicad_utils_py_include_directories kicad_utils_py INCLUDE_DIRECTORIES) +target_include_directories(generate-header PRIVATE ${kicad_utils_py_include_directories}) +target_link_libraries(generate-header PUBLIC ${PYTHON_LIBRARIES} stdc++fs) + +add_dependencies(generate-header kicad_utils_py) + +install(TARGETS generate-header + EXPORT kicad_utils_export + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib) + +install(DIRECTORY ../template/ + DESTINATION share/kicad-utils/template + FILES_MATCHING PATTERN *.py + PERMISSIONS OWNER_READ GROUP_READ WORLD_READ) + +install(DIRECTORY ../cmake/ + DESTINATION lib/cmake/KicadUtils) + +install(EXPORT kicad_utils_export + FILE KicadUtilsTargets.cmake + NAMESPACE KicadUtils:: + DESTINATION lib/cmake/KicadUtils) diff --git a/cli/generate-header.cpp b/cli/generate-header.cpp new file mode 100644 index 0000000..1033fc3 --- /dev/null +++ b/cli/generate-header.cpp @@ -0,0 +1,466 @@ +#include "trygvis/kicad.h" + +using namespace trygvis::kicad; +using trygvis::kicad::netlist::component; +using trygvis::kicad::netlist::kicad_net_loader; +using trygvis::kicad::netlist::kicad_parse_exception; +using trygvis::kicad::netlist::net; +using nl = trygvis::kicad::netlist::netlist; + +#include "pybind11/pybind11.h" +#include "pybind11/stl.h" +#include "pybind11/eval.h" + +namespace py = pybind11; + +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +static const char PATH_SEPARATOR = ';'; +#else +static const char PATH_SEPARATOR = ':'; +#endif + +using namespace std; +namespace fs = std::experimental::filesystem; + +char *program; + +__attribute__((noreturn)) +void usage(const char *reason = nullptr) { + if (reason) { + fprintf(stderr, "%s\n", reason); + } + fprintf(stderr, "usage: %s -n -r [-o ] [-t ]\n", program); + exit(EX_USAGE); +} + +void parse_args(int argc, char **argv, bool *debug, char **net_filename, char **ref, char **py_template, + vector &template_libs, char **out_filename) { + *debug = false; + *ref = nullptr; + *out_filename = nullptr; + *py_template = nullptr; + *ref = nullptr; + + int c; + while ((c = getopt(argc, argv, "Dn:r:o:t:l:")) != -1) { + switch (c) { + case 'D': + *debug = true; + break; + case 'n': + *net_filename = optarg; + break; + case 'r': + *ref = optarg; + break; + case 't': + *py_template = optarg; + break; + case 'l': + template_libs.push_back(fs::path(optarg)); + break; + case 'o': + if (strcmp("-", optarg) != 0) { + *out_filename = optarg; + } + break; + default: + usage(); + } + } + + if (!*net_filename || !*ref) { + usage(); + } +} + +class part { +public: + part(const string &name) : name(name) { + } + + virtual ~part() { + } + + const string name; + + virtual bool generate(stringstream &out, const string &ref, const vector usages) = 0; +}; + +class arduino_uno : public part { +public: + arduino_uno() : part("ARDUINO_UNO") {} + + ~arduino_uno() { + } + + virtual bool generate(stringstream &out, const string &ref, const vector usages) override { + out << "namespace schematic {" << endl; + for (auto &usage: usages) { + auto node = usage->node_for_ref(ref); + + if (node->pin >= 7 && node->pin <= 12) { + out << "static const int ANALOG_" << usage->name << " = " << (node->pin - 7) << ";" << endl; + } else if (node->pin >= 13 && node->pin <= 26) { + out << "static const int " << usage->name << " = " << (node->pin - 13) << ";" << endl; + } + } + out << "} // namespace schematic" << endl; + + return true; + } +}; + +class intel_quark_d2000 : public part { +public: + intel_quark_d2000() : part("INTEL_QUARK_D2000") {} + + ~intel_quark_d2000() { + } + + virtual bool generate(stringstream &out, const string &ref, const vector usages) override { + static map gpio_map{ + {2, "10"}, + {3, "11"}, + {4, "12"}, + {5, "13"}, + {6, "14"}, + {7, "15"}, + {8, "16"}, + {9, "17"}, + {10, "18"}, + + {11, "9"}, + {13, "20"}, + {14, "21"}, + {15, "22"}, + {16, "23"}, + {18, "19"}, + + {21, "24"}, + + {31, "0"}, + {32, "1"}, + {33, "2"}, + {34, "3"}, + {35, "4"}, + {36, "5"}, + {37, "6"}, + {38, "7"}, + {39, "8"}, + }; + + out << "#include " << endl; + out << "#include " << endl; + out << endl; + out << "enum schematic_direction {" << endl; + out << " schematic_direction_out = 1," << endl; + out << " schematic_direction_in = 2" << endl; + out << "};" << endl;; + + for (auto &usage: usages) { + auto node = usage->node_for_ref(ref); + + auto gpio = gpio_map.find(node->pin); + + if (gpio == gpio_map.end()) { + continue; + } + + out << "static const uint8_t SCHEMATIC_" << usage->name << " = " << gpio->second << ";" << endl; + out << endl; + out << "static inline" << endl; + out << "qm_rc_t schematic_PUSH_BUTTON_direction(enum schematic_direction dir) {" << endl; + out << " qm_gpio_port_config_t cfg;" << endl; + out << "" << endl; + out << " qm_gpio_get_config(QM_GPIO_0, &cfg);" << endl; + out << "" << endl; + out << " if (dir == schematic_direction_out) {" << endl; + out << " cfg.direction |= BIT(SCHEMATIC_PUSH_BUTTON);" << endl; + out << " } else {" << endl; + out << " cfg.direction &= ~BIT(SCHEMATIC_PUSH_BUTTON);" << endl; + out << " }" << endl; + out << "" << endl; + out << " return qm_gpio_set_config(QM_GPIO_0, &cfg);" << endl; + out << "}" << endl; + out << endl; + } + + return true; + } +}; + +//class library { +//public: +// library(const string &name) : name(name) {} +// +// library(library &&l) : name(std::move(l.name)), parts(std::move(l.parts)) { } +// +// virtual ~library() { } +// +// const string name; +// vector> parts; +// +// opt find_part(string name) const { +// for (auto &part: parts) { +// if (part->name == name) { +// return part.get(); +// } +// } +// +// return nullopt; +// } +//}; + +//class kicad_gen { +//public: +// vector libraries; +// +// kicad_gen() { +// library kicad_utils("kicad_utils"); +// kicad_utils.parts.push_back(make_unique()); +// kicad_utils.parts.push_back(make_unique()); +// +// libraries.push_back(std::move(kicad_utils)); +// } +// +// opt find_library(string name) { +// for (auto &l : libraries) { +// if (l.name == name) { +// return &l; +// } +// } +// +// return nullopt; +// } +//}; + +opt find_template(const string &py_template, const vector &paths) { + + fs::path p(py_template); + + if (fs::exists(p)) { + return p; + } + + for (auto &&prefix : paths) { + p = prefix / py_template; + + if (fs::exists(p)) { + return p; + } + } + + return nullopt; +} + +/* +bool generate(const char *ref, nl *const netlist, stringstream &out) { + kicad_gen gen; + opt componentO = netlist->find_component(ref); + + if (!componentO) { + cerr << "Could not find component '" << ref << "'" << endl; + return false; + } + + const component *c = *componentO; + + cerr << "Generating connections for " << c->ref << " (" << c->value << ")" << endl; + + auto libraryO = gen.find_library(c->_lib_source.lib); + + if (!libraryO) { + cerr << "Unsupported library '" << c->_lib_source.lib << "'" << endl; + return false; + } + + const library *library = *libraryO; + + auto partName = c->_lib_source.part; + + auto partO = library->find_part(partName); + + if (!partO) { + cerr << "The library library " << library->name << " does not have a component named " << partName << endl; + return false; + } + + part *part = *partO; + + out << "#ifndef SCHEMATIC_H" << endl; + out << "#define SCHEMATIC_H" << endl; + out << endl; + out << "*/ +/*" << endl; + out << "THIS FILE IS GENERATED. DO NOT EDIT." << endl; + out << endl; + out << "Generated from schematic for reference " << ref << ", part " << partName << " in library " << + library->name << "." << endl; + out << "*//* +" << endl; + out << endl; + + auto usages = netlist->find_usage_of(ref); + + part->generate(out, ref, usages); + + out << "#endif // SCHEMATIC_H" << endl; + + return true; +} +*/ + +bool generate_py(const char *ref, nl *const netlist, stringstream &out, const fs::path &py_template) { + py::dict vars; + pybind11::module kicad_utils_py_module = py::module::import("kicad_utils_py"); + vars = kicad_utils_py_module.attr("__dict__"); + + pybind11::object netlist_py = py::cast(netlist); + + vars["netlist"] = netlist_py; + vars["ref"] = py::cast(ref); + + string init; + init += "generateHeader = GenerateHeader(ref, netlist)\n"; + init += "del ref\n"; + init += "del netlist\n"; + py::eval(init, vars); + + py::eval_file(py_template.string(), vars); + + string str = py::eval("generateHeader.str", vars).cast().cast(); + out << str; + + return true; +} + +int main(int argc, char **argv) { + bool debug; + char *net_filename; + char *ref; + char *py_template; + vector template_libs; + char *out_filename; + + fs::path bin(argv[0]); + fs::path basedir = bin.parent_path().parent_path(); + + template_libs.push_back(fs::current_path()); + template_libs.push_back(basedir / "share" / "kicad-utils" / "template"); + + parse_args(argc, argv, &debug, &net_filename, &ref, &py_template, template_libs, &out_filename); + + kicad_net_loader loader; + + /* + string python_version = Py_GetVersion(); + cout << "Python version: " << python_version << endl; + */ + + /* + map env; + for (char **e = environ; *e != NULL; e++) { + string s = *e; + auto i = s.find('='); + env[s.substr(0, i)] = s.substr(i + 1); + } + + for (auto &&e: env) { + cout << e.first << " = " << e.second << endl; + } + */ + + try { + nl netlist = loader.load(net_filename, cerr); + + stringstream out; + bool ok = false; + + if (py_template) { + cerr << "Template path: " << endl; + for (auto &&p : template_libs) { + cerr << " " << p.string() << endl; + } + + opt tmpl = find_template(py_template, template_libs); + + if (!tmpl) { + cerr << "No such file: " << py_template << endl; + cerr << "Search path: " << endl; + for (auto &&p : template_libs) { + cerr << " " << p.string() << endl; + } + cerr << "Current path is " << fs::current_path().string() << endl; + } else { + wchar_t *program = Py_DecodeLocale(argv[0], NULL); + if (program == NULL) { + fprintf(stderr, "Fatal error: cannot decode argv[0]\n"); + exit(1); + } + Py_SetProgramName(program); + + vector module_paths{ + basedir / "python", + basedir / "lib", + }; + + std::wstring_convert > converter; + converter.from_bytes(string("foo")); + + wstring pythonpath(Py_GetPath()); + for (auto &path : module_paths) { + if (!fs::exists(path)) { + continue; + } +// pythonpath += converter.from_bytes(PATH_SEPARATOR + path.string()); + pythonpath = converter.from_bytes(path.string() + PATH_SEPARATOR) + pythonpath; + } + wcerr << "pythonpath=" << pythonpath << endl; + Py_SetPath(pythonpath.c_str()); + + cerr << "Compile-time Python version: " << PY_VERSION << endl; + cerr << "Current python version: " << Py_GetVersion() << endl; + + Py_Initialize(); + try { + ok = generate_py(ref, &netlist, out, tmpl.value()); + } catch (py::error_already_set &e) { + cerr << "Python failure:" << endl << e.what() << endl; + } catch (std::runtime_error &e) { + cerr << "Generation failed: " << e.what() << endl; + } + Py_Finalize(); + PyMem_RawFree(program); + } +// } else { +// ok = generate(ref, &netlist, out); + } + + if (ok) { + auto contents = out.str(); + if (out_filename != nullptr) { + ofstream outstream(out_filename); + outstream << contents; + outstream.close(); + } else { + cout << contents << flush; + } + return EXIT_SUCCESS; + } + return EXIT_FAILURE; + } catch (kicad_parse_exception &e) { + for (auto &m: e.messages) { + fprintf(stderr, "%s\n", m.c_str()); + } + errx(EX_DATAERR, "Could not parse netlist"); + } +} diff --git a/cmake/KicadUtilsConfig.cmake b/cmake/KicadUtilsConfig.cmake new file mode 100644 index 0000000..2302fe2 --- /dev/null +++ b/cmake/KicadUtilsConfig.cmake @@ -0,0 +1,2 @@ +include("${CMAKE_CURRENT_LIST_DIR}/kicad_generate_header.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/KicadUtilsTargets.cmake") diff --git a/cmake/kicad_generate_header.cmake b/cmake/kicad_generate_header.cmake new file mode 100644 index 0000000..930c5ed --- /dev/null +++ b/cmake/kicad_generate_header.cmake @@ -0,0 +1,57 @@ +function(kicad_generate_header) + set(options IN_SOURCE) + set(oneValueArgs OUTPUT NET REF OUTPUT_DIR TEMPLATE TEMPLATE_LIB_LIST) + + cmake_parse_arguments(kicad_gen "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if (NOT kicad_gen_OUTPUT) + message(FATAL_ERROR "OUTPUT is required") + endif () + set(output "${kicad_gen_OUTPUT}") + + if (NOT kicad_gen_NET) + message(FATAL_ERROR "NET is required") + endif () + set(NET "${CMAKE_CURRENT_SOURCE_DIR}/${kicad_gen_NET}") + + if (NOT kicad_gen_REF) + message(FATAL_ERROR "REF is required") + endif () + set(REF "${kicad_gen_REF}") + + if (kicad_gen_OUTPUT_DIR) + if (kicad_gen_IN_SOURCE) + message(FATAL_ERROR "IN_SOURCE can't be used if OUTPUT_DIR is used") + endif () + + set(output_dir "${kicad_gen_OUTPUT_DIR}") + set(output_rel_dir "${output_dir}") + else () + if (kicad_gen_IN_SOURCE) + set(output_dir "${CMAKE_CURRENT_SOURCE_DIR}") + set(output_rel_dir "${CMAKE_SOURCE_DIR}") + else () + set(output_dir "${CMAKE_CURRENT_BINARY_DIR}") + set(output_rel_dir "${CMAKE_BINARY_DIR}") + endif () + endif () + + if (kicad_gen_TEMPLATE) + set(t "-t") + set(template "${kicad_gen_TEMPLATE}") + endif () + + set(lib "-l;${CMAKE_CURRENT_SOURCE_DIR}") + foreach(l IN LISTS kicad_gen_TEMPLATE_LIB_LIST) + list(APPEND lib -l) + list(APPEND lib "${l}") + endforeach () + + set(output_file "${output_dir}/${output}") + file(RELATIVE_PATH output_file_rel "${output_rel_dir}" ${output_file}) + + add_custom_command(OUTPUT "${output_file}" + COMMAND KicadUtils::generate-header -n ${NET} -r ${REF} -o ${output_file} ${t} ${template} ${lib} + MAIN_DEPENDENCY ${NET} + COMMENT "Generating ${output_file_rel}") +endfunction() diff --git a/configure b/configure new file mode 100755 index 0000000..4aaae1c --- /dev/null +++ b/configure @@ -0,0 +1,51 @@ +#!/bin/bash + +PROJECT=kicad-utils + +set -e + +CMAKE=`which cmake 2>/dev/null` +PREFIX=$HOME/opt/$PROJECT +# TODO: find basedir from the current binary +BASEDIR=`pwd` + +if [ ! -x "$CMAKE" ] +then + echo "cmake is not installed" + exit 1 +fi + +cd $BASEDIR + +NEW=`test -d build && echo 1 || true` + +mkdir -p build + +CMAKE_OPTS=() +CMAKE_OPTS+=(-DCMAKE_INSTALL_PREFIX=$PREFIX) +#CMAKE_OPTS+=(-DCMAKE_SKIP_RPATH=NO) +#CMAKE_OPTS+=(-DCMAKE_SKIP_INSTALL_RPATH=NO) +# These are required until Antlr C++ is installed by the system +CMAKE_OPTS+=(-DCMAKE_INSTALL_RPATH_USE_LINK_PATH=TRUE) +CMAKE_OPTS+=(-DAntlr4_DIR=$HOME/opt/antlr4-cpp/lib/cmake/Antlr4) + +cd build +echo "" +echo "Generating build.." +echo "" +cmake "${CMAKE_OPTS[@]}" .. + + +echo "" +echo "" +echo "" + +if [[ $NEW == 1 ]] +then + echo "Reusing existing build/ directory. You should probably run cd build && make clean" +else + echo "The build files are generated in build/. To build run: cd build && make install" + echo "The binaries will be installed under $PREFIX" +fi + +echo "" diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt new file mode 100644 index 0000000..cbf8e78 --- /dev/null +++ b/core/CMakeLists.txt @@ -0,0 +1,22 @@ +find_package(Antlr4) + +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + +antlr4_add_target( + TARGET KicadNet + LEXER KicadNetLexer.g4 + PARSER KicadNetParser.g4 + STATIC) + +add_library(kicad-utils-core + kicad.cpp) +target_sources(kicad-utils-core PRIVATE $) + +get_target_property(KicadNet_includes KicadNet INCLUDE_DIRECTORIES) + +target_include_directories(kicad-utils-core + PUBLIC include + PRIVATE include-priv ${KicadNet_includes}) + +target_link_libraries(kicad-utils-core + Antlr4::antlr4_shared) diff --git a/core/KicadNetLexer.g4 b/core/KicadNetLexer.g4 new file mode 100644 index 0000000..e10f7e7 --- /dev/null +++ b/core/KicadNetLexer.g4 @@ -0,0 +1,38 @@ +lexer grammar KicadNetLexer; + +LPAREN: '('; +RPAREN: ')'; + +QUOTE: '"'; + +CODE: 'code'; +COMP: 'comp'; +FIELD: 'field'; +LIBSOURCE: 'libsource'; +LIBPART: 'libpart'; +LIB: 'lib'; +NAME: 'name'; +NET: 'net'; +NODE: 'node'; +NUM: 'num'; +PART: 'part'; +PIN: 'pin'; +REF: 'ref'; +TYPE: 'type'; +VALUE: 'value'; + +STRING: '"' ~["]* '"'; + +INTEGER: [0-9]+; + +ID + : [/+~\_\-\.\*\?/a-zA-Z0-9]+ + ; + +BlockComment + : '/*' .*? '*/' -> skip + ; + +WS + : [ \t\r\n]+ -> skip + ; diff --git a/core/KicadNetParser.g4 b/core/KicadNetParser.g4 new file mode 100644 index 0000000..3971111 --- /dev/null +++ b/core/KicadNetParser.g4 @@ -0,0 +1,89 @@ +parser grammar KicadNetParser; + +options { + tokenVocab = KicadNetLexer; +} + +file: + form+ + ; + +form: + name #formName + | component #formComponent + | field #formField + | net #formNet + | node #formNode + | pinDecl #formDecl + | ref #formRef + | value #formValue + | libpart #formLibpart + | keyValue #formKeyValue + | string #formString + ; + +code: + '(' 'code' INTEGER ')' + ; + +component: + '(' 'comp' ref value libsource keyValue* ')' + ; + +field: + '(' 'field' name string ')' + ; + +name: + '(' 'name' string ')' + ; + +net: + '(' 'net' code name node+ ')' + ; + +node: + '(' 'node' ref pinRef ')' + ; + +pinRef: + '(' 'pin' INTEGER ')' + ; + +pinDecl: + '(' 'pin' '(' 'num' INTEGER ')' '(' 'name' string ')' '(' 'type' string ')' ')' + ; + +ref: + '(' 'ref' string ')' + ; + +value: + '(' 'value' string ')' + ; + +lib: + '(' 'lib' string ')' + ; + +part: + '(' 'part' string ')' + ; + +libpart: + '(' 'libpart' lib part keyValue* ')' + ; + +libsource: + '(' 'libsource' lib part ')' + ; + +keyValue: + '(' string form* ')' + ; + +string: + ID #stringId + | INTEGER #stringInt + | STRING #stringText + ; diff --git a/core/include-priv/trygvis/antlr.h b/core/include-priv/trygvis/antlr.h new file mode 100644 index 0000000..f5656ea --- /dev/null +++ b/core/include-priv/trygvis/antlr.h @@ -0,0 +1,67 @@ +#pragma once + +#include "antlr4-runtime.h" + +namespace trygvis { +namespace antlr { + +// This namespace is shared copied code + +using ParseTree = antlr4::tree::ParseTree; + +class MissingParseTreeProperty : public std::out_of_range { +public: + explicit MissingParseTreeProperty(const std::string &what) : out_of_range(what) { } +}; + +template +class ParseTreeProperty { +public: + virtual V get(Ref node) { + return get(node.get()); + } + + virtual V get(ParseTree *const node) { + if (!debug) { + return _annotations.at(node); + } + + try { +// cerr << "node = " << node->getText() << endl; + return _annotations.at(node); + } catch (std::out_of_range &e) { + std::cerr << "get(" << node << "), text=" << node->getText() << std::endl; + std::stringstream buf; + buf << "out of range: " << node << ", text=" << node->getText(); + auto msg = buf.str(); + std::cerr << msg << std::endl; + throw MissingParseTreeProperty(msg); + } + } + + virtual void put(ParseTree *const node, V value) { + if (debug) { + std::cerr << "put(" << node << ", " << value << "), text: " << node->getText() << std::endl; + } + _annotations[node] = value; + } + + virtual V removeFrom(ParseTree *const node) { + auto it = _annotations.find(node); + + if (it == _annotations.end()) { + throw MissingParseTreeProperty(node->getText()); + } + + return it->second; + } + +protected: + std::map _annotations; + +private: +}; + +} // namespace antlr + +} diff --git a/core/include-priv/trygvis/string_utils.h b/core/include-priv/trygvis/string_utils.h new file mode 100644 index 0000000..9902a3f --- /dev/null +++ b/core/include-priv/trygvis/string_utils.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +namespace trygvis { +namespace string_utils { + +/** + * Check if a starts with b. + */ +static bool startsWith(const std::string &a, const std::string &b) { + return b.length() <= a.length() && a.compare(0, b.length(), b) == 0; +} + +/** + * Check if a ends with b. + */ +static bool endsWith(const std::string &a, const std::string &b) { + return b.length() <= a.length() && a.compare(a.length() - b.length(), b.length(), b) == 0; +} + +} // namespace string_utils +} // namespace trygvis diff --git a/core/include/trygvis/kicad.h b/core/include/trygvis/kicad.h new file mode 100644 index 0000000..0c5005d --- /dev/null +++ b/core/include/trygvis/kicad.h @@ -0,0 +1,102 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace trygvis { +namespace kicad { +template +using opt = std::experimental::optional; +using std::experimental::nullopt; +} // namespace kicad +} // namespace trygvis + +namespace trygvis { +namespace kicad { +namespace netlist { + +struct lib_source { + const std::string lib; + const std::string part; + + lib_source(const std::string &lib, const std::string &part) : lib(lib), part(part) {} +}; + +struct component { + const std::string ref; + const std::string value; + const lib_source _lib_source; + + component(const std::string &ref, const std::string &value, const lib_source &_lib_source) : + ref(ref), value(value), _lib_source(_lib_source) {} +}; + +struct pin { + int num; + std::string name; + std::string type; +}; + +struct part { + std::vector pins; +}; + +class node { +public: + const std::string ref; + const int pin; + + node(const std::string &ref, int pin) : ref(ref), pin(pin) {} +}; + +class net { +public: + const int code; + const std::string name; + const std::vector nodes; + + net(int code, const std::string &name, const std::vector &nodes) : code(code), name(name), nodes(nodes) {} + + const node *node_for_ref(const std::string &ref) const; +}; + +struct netlist { + std::vector components; + std::vector parts; + std::vector nets; + + opt find_component(const std::string &ref) const; + + std::vector find_usage_of(const std::string &ref) const; +}; + +class kicad_parse_exception : public std::runtime_error { +public: + explicit kicad_parse_exception(const std::vector &messages) : + runtime_error("Parse error"), messages(messages) {} + + ~kicad_parse_exception() {} + + const std::vector messages; +}; + +class kicad_net_loader { +public: + kicad_net_loader(); + + virtual ~kicad_net_loader(); + + netlist load(std::string path, std::ostream &err); + + void setDebug(bool debug); + +private: + bool debug_; +}; + +} // namespace netlist +} // namespace kicad +} // namespace trygvis diff --git a/core/kicad.cpp b/core/kicad.cpp new file mode 100644 index 0000000..1a6ca2f --- /dev/null +++ b/core/kicad.cpp @@ -0,0 +1,194 @@ +#include "trygvis/kicad.h" +#include "trygvis/antlr.h" +#include "trygvis/string_utils.h" +#include +#include +#include +#include +#include + +using namespace std; +using namespace trygvis::antlr; +using namespace trygvis::string_utils; + +namespace trygvis { +namespace kicad { +namespace netlist { + +const node *net::node_for_ref(const std::string &ref) const { + for (auto &node: nodes) { + if (node.ref == ref) { + return &node; + } + } + + return nullptr; +} + +opt netlist::find_component(const string &ref) const { + for (const component &c :components) { + int x = c.ref.compare(ref); + if (x == 0) { + return &c; + } + } + return std::experimental::nullopt; +} + +vector netlist::find_usage_of(const string &ref) const { + vector usage; + + for (auto &net : nets) { + if (net.nodes.size() <= 1) { + continue; + } + + for (auto &node: net.nodes) { + if (node.ref == ref) { + usage.push_back(&net); + } + } + } + + return usage; +} + +static +int parse(const Ref &integer) { + unsigned long long i = strtoull(integer->getText().c_str(), NULL, 10); + + return static_cast(i); +} + +class KicadErrorListener : public BaseErrorListener { +public: + vector messages; + + void syntaxError(IRecognizer *recognizer, Token *offendingSymbol, size_t line, int charPositionInLine, + const string &msg, exception_ptr e) override { + static_cast(recognizer); + static_cast(offendingSymbol); + static_cast(e); + messages.push_back("line " + to_string(line) + ":" + to_string(charPositionInLine) + ": " + msg); + } +}; + +class kicad_main_listener : public KicadNetParserBaseListener { + +public: + vector components; + vector parts; + vector nets; + vector nodes; + + ParseTreeProperty strings; + + virtual void exitNet(KicadNetParser::NetContext *ctx) override { + auto code = parse(ctx->code()->INTEGER()); + auto name = strings.get(ctx->name()->string()); + + if (startsWith(name, "/")) { + name = name.substr(1); + } + +// cerr << "exitNet: " << "code=" << code << ", name=" << name << ", nodes=" << nodes.size() << endl; + +// if (nodes.size() > 1) { +// cerr << "Net#" << code << ": " << name << endl; +// for (auto &node: nodes) { +// cerr << " Node: " << node.ref << "#" << node.pin << endl; +// } +// } + nets.emplace_back(code, name, nodes); + nodes.clear(); + } + + virtual void exitNode(KicadNetParser::NodeContext *ctx) override { + auto ref = strings.get(ctx->ref()->string()); + auto pin = parse(ctx->pinRef()->INTEGER()); + +// cerr << "exitNode: " << "ref=" << ref << ", pin=" << pin << endl; + + nodes.emplace_back(ref, pin); + } + + virtual void exitComponent(KicadNetParser::ComponentContext *ctx) override { + auto ref = strings.get(ctx->ref()->string()); + auto value = strings.get(ctx->value()->string()); + + lib_source ls{strings.get(ctx->libsource()->lib()->string()), + strings.get(ctx->libsource()->part()->string())}; + + components.emplace_back(ref, value, ls); + } + + virtual void exitStringId(KicadNetParser::StringIdContext *ctx) override { + strings.put(ctx, ctx->getText()); + } + + virtual void exitStringInt(KicadNetParser::StringIntContext *ctx) override { + strings.put(ctx, ctx->getText()); + } + + virtual void exitStringText(KicadNetParser::StringTextContext *ctx) override { + auto s = ctx->getText(); + strings.put(ctx, s.substr(1, s.length() - 2)); + } +}; + +netlist kicad_net_loader::load(string path, ostream &err) { + + ANTLRFileStream input(path); + KicadNetLexer lexer(&input); + CommonTokenStream tokens(&lexer); + + tokens.fill(); + + if (debug_) { + for (auto token : tokens.getTokens()) { + err << token->toString() << endl; + } + } + + KicadNetParser parser(&tokens); + parser.removeErrorListeners(); + KicadErrorListener errorListener; + parser.addErrorListener(&errorListener); + + parser.file(); + + if (!errorListener.messages.empty()) { + throw kicad_parse_exception(errorListener.messages); + } + + parser.reset(); + + kicad_main_listener mainListener; + parser.addParseListener(&mainListener); + + auto file = parser.file(); + + if (debug_ && parser.getNumberOfSyntaxErrors() == 0) { + err << file->toStringTree(&parser) << endl; + } + + return netlist { + mainListener.components, + mainListener.parts, + mainListener.nets, + }; +} + +kicad_net_loader::kicad_net_loader() : debug_(false) { +} + +kicad_net_loader::~kicad_net_loader() { +} + +void kicad_net_loader::setDebug(bool debug) { + debug_ = debug; +} + +} // namespace netlist +} // namespace trygvis +} // namespace kicad diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 04baca4..58048ff 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,2 +1,11 @@ +cmake_minimum_required(VERSION 3.5) +project(kicad_utils_examples) + +find_package(KicadUtils QUIET) + +if (NOT KicadUtils_FOUND) + message(FATAL_ERROR "The KicadUtils CMake package was not found. Did you pass the correct value for KicadUtils_DIR?\nIt should probably be something like KicadUtils_DIR=$HOME/opt/kicad-utils/lib/cmake/KicadUtils") +endif () + add_subdirectory(arduino-led) add_subdirectory(intel-quark-d2000) diff --git a/examples/arduino-led/CMakeLists.txt b/examples/arduino-led/CMakeLists.txt index 06ef920..a615fb7 100644 --- a/examples/arduino-led/CMakeLists.txt +++ b/examples/arduino-led/CMakeLists.txt @@ -1,3 +1,5 @@ +find_package(KicadUtils) + kicad_generate_header( OUTPUT schematic.h NET schematic/arduino-led.net diff --git a/examples/intel-quark-d2000/CMakeLists.txt b/examples/intel-quark-d2000/CMakeLists.txt index 1822996..3920d4b 100644 --- a/examples/intel-quark-d2000/CMakeLists.txt +++ b/examples/intel-quark-d2000/CMakeLists.txt @@ -1,8 +1,11 @@ +find_package(KicadUtils) + kicad_generate_header( OUTPUT schematic.h NET schematic/intel-quark-d2000.net REF U1 - IN_SOURCE) + TEMPLATE intel-quark-d2000.py + TEMPLATE_LIB_LIST .) if (FALSE) # If you have Intel Quark support for CMake something like this would work: @@ -10,5 +13,5 @@ if (FALSE) #add_executable(arduino-led arduino-led.ino schematic.h) #target_include_directories(arduino-led PUBLIC ${CMAKE_CURRENT_BINARY_DIR}/kicad-include) else () - add_custom_target(intel-quark-d2000 ALL DEPENDS schematic.h) + add_custom_target(intel-quark-d2000 ALL DEPENDS schematic.h schematic-py.h) endif () diff --git a/examples/intel-quark-d2000/intel-quark-d2000.py b/examples/intel-quark-d2000/intel-quark-d2000.py new file mode 100644 index 0000000..b69a919 --- /dev/null +++ b/examples/intel-quark-d2000/intel-quark-d2000.py @@ -0,0 +1,2 @@ +# noinspection PyUnresolvedReferences +generateHeader.write("foo") diff --git a/generate-header.cpp b/generate-header.cpp deleted file mode 100644 index d134d47..0000000 --- a/generate-header.cpp +++ /dev/null @@ -1,311 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "trygvis/kicad.h" - -using namespace std; -using namespace trygvis::kicad; - -using trygvis::kicad::netlist::component; -using trygvis::kicad::netlist::kicad_net_loader; -using trygvis::kicad::netlist::kicad_parse_exception; -using trygvis::kicad::netlist::net; - -char *program; - -__attribute__((noreturn)) -void usage(const char *reason = nullptr) { - if (reason) { - fprintf(stderr, "%s\n", reason); - } - fprintf(stderr, "usage: %s -n -r [-o ]\n", program); - exit(EX_USAGE); -} - -void parse_args(int argc, char **argv, bool *debug, char **net_filename, char **ref, char **out_filename) { - *debug = false; - *ref = nullptr; - *out_filename = nullptr; - *ref = nullptr; - - int c; - while ((c = getopt(argc, argv, "Dn:r:o:")) != -1) { - switch (c) { - case 'D': - *debug = true; - break; - case 'n': - *net_filename = optarg; - break; - case 'r': - *ref = optarg; - break; - case 'o': - if (strcmp("-", optarg) != 0) { - *out_filename = optarg; - } - break; - default: - usage(); - } - } - - if (!*net_filename && !*ref) { - usage(); - } -} - -class part { -public: - part(const string &name) : name(name) { - } - - virtual ~part() { - } - - const string name; - - virtual bool generate(stringstream &out, const string &ref, const vector usages) = 0; -}; - -class arduino_uno : public part { -public: - arduino_uno() : part("ARDUINO_UNO") { } - - ~arduino_uno() { - } - - virtual bool generate(stringstream &out, const string &ref, const vector usages) override { - out << "namespace schematic {" << endl; - for (auto &usage: usages) { - auto node = usage->node_for_ref(ref); - - if (node->pin >= 7 && node->pin <= 12) { - out << "static const int ANALOG_" << usage->name << " = " << (node->pin - 7) << ";" << endl; - } else if (node->pin >= 13 && node->pin <= 26) { - out << "static const int " << usage->name << " = " << (node->pin - 13) << ";" << endl; - } - } - out << "} // namespace schematic" << endl; - - return true; - } -}; - -class intel_quark_d2000 : public part { -public: - intel_quark_d2000() : part("INTEL_QUARK_D2000") { } - - ~intel_quark_d2000() { - } - - virtual bool generate(stringstream &out, const string &ref, const vector usages) override { - static map gpio_map{ - {2, "10"}, - {3, "11"}, - {4, "12"}, - {5, "13"}, - {6, "14"}, - {7, "15"}, - {8, "16"}, - {9, "17"}, - {10, "18"}, - - {11, "9"}, - {13, "20"}, - {14, "21"}, - {15, "22"}, - {16, "23"}, - {18, "19"}, - - {21, "24"}, - - {31, "0"}, - {32, "1"}, - {33, "2"}, - {34, "3"}, - {35, "4"}, - {36, "5"}, - {37, "6"}, - {38, "7"}, - {39, "8"}, - }; - - out << "#include " << endl; - out << "#include " << endl; - out << endl; - out << "enum schematic_direction {" << endl; - out << " schematic_direction_out = 1," << endl; - out << " schematic_direction_in = 2" << endl; - out << "};" << endl;; - - for (auto &usage: usages) { - auto node = usage->node_for_ref(ref); - - auto gpio = gpio_map.find(node->pin); - - if (gpio == gpio_map.end()) { - continue; - } - - out << "static const uint8_t SCHEMATIC_" << usage->name << " = " << gpio->second << ";" << endl; - out << endl; - out << "static inline" << endl; - out << "qm_rc_t schematic_PUSH_BUTTON_direction(enum schematic_direction dir) {" << endl; - out << " qm_gpio_port_config_t cfg;" << endl; - out << "" << endl; - out << " qm_gpio_get_config(QM_GPIO_0, &cfg);" << endl; - out << "" << endl; - out << " if (dir == schematic_direction_out) {" << endl; - out << " cfg.direction |= BIT(SCHEMATIC_PUSH_BUTTON);" << endl; - out << " } else {" << endl; - out << " cfg.direction &= ~BIT(SCHEMATIC_PUSH_BUTTON);" << endl; - out << " }" << endl; - out << "" << endl; - out << " return qm_gpio_set_config(QM_GPIO_0, &cfg);" << endl; - out << "}" << endl; - out << endl; - } - - return true; - } -}; - -class library { -public: - library(const string &name) : name(name) { } - - library(library &&l) : name(std::move(l.name)), parts(std::move(l.parts)) { } - - const string name; - vector> parts; - - opt find_part(string name) const { - for (auto &part: parts) { - if (part->name == name) { - return part.get(); - } - } - - return nullopt; - } -}; - -class kicad_gen { -public: - vector libraries; - - kicad_gen() { - library kicad_utils("kicad_utils"); - kicad_utils.parts.push_back(make_unique()); - kicad_utils.parts.push_back(make_unique()); - - libraries.push_back(std::move(kicad_utils)); - } - - opt find_library(string name) { - for (auto &l : libraries) { - if (l.name == name) { - return &l; - } - } - - return nullopt; - } -}; - -bool generate(const char *ref, const trygvis::kicad::netlist::netlist &netlist, stringstream &out) { - kicad_gen gen; - opt componentO = netlist.find_component(ref); - - if (!componentO) { - cerr << "Could not find component '" << ref << "'" << endl; - return false; - } - - const component *c = *componentO; - - cerr << "Generating connections for " << c->ref << " (" << c->value << ")" << endl; - - auto libraryO = gen.find_library(c->_lib_source.lib); - - if (!libraryO) { - cerr << "Unsupported library '" << c->_lib_source.lib << "'" << endl; - return false; - } - - const library *library = *libraryO; - - auto partName = c->_lib_source.part; - - auto partO = library->find_part(partName); - - if (!partO) { - cerr << "The library library " << library->name << " does not have a component named " << partName << endl; - return false; - } - - part *part = *partO; - - out << "#ifndef SCHEMATIC_H" << endl; - out << "#define SCHEMATIC_H" << endl; - out << endl; - out << "/*" << endl; - out << "THIS FILE IS GENERATED. DO NOT EDIT." << endl; - out << endl; - out << "Generated from schematic for reference " << ref << ", part " << partName << " in library " << - library->name << "." << endl; - out << "*/" << endl; - out << endl; - - auto usages = netlist.find_usage_of(ref); - - part->generate(out, ref, usages); - - out << "#endif // SCHEMATIC_H" << endl; - - return true; -} - -int main(int argc, char **argv) { - program = argv[0]; - - bool debug; - char *net_filename; - char *ref; - char *out_filename; - parse_args(argc, argv, &debug, &net_filename, &ref, &out_filename); - - kicad_net_loader loader; - - try { - auto netlist = loader.load(net_filename); - - stringstream out; - auto ok = generate(ref, netlist, out); - - if (ok) { - auto contents = out.str(); - if (out_filename != nullptr) { - ofstream outstream(out_filename); - outstream << contents; - outstream.close(); - } else { - cout << contents << flush; - } - return EXIT_SUCCESS; - } - return EXIT_FAILURE; - } catch (kicad_parse_exception &e) { - for (auto &m: e.messages) { - fprintf(stderr, "%s\n", m.c_str()); - } - errx(EX_DATAERR, "Could not parse netlist"); - } -} diff --git a/include-priv/trygvis/antlr.h b/include-priv/trygvis/antlr.h deleted file mode 100644 index f5656ea..0000000 --- a/include-priv/trygvis/antlr.h +++ /dev/null @@ -1,67 +0,0 @@ -#pragma once - -#include "antlr4-runtime.h" - -namespace trygvis { -namespace antlr { - -// This namespace is shared copied code - -using ParseTree = antlr4::tree::ParseTree; - -class MissingParseTreeProperty : public std::out_of_range { -public: - explicit MissingParseTreeProperty(const std::string &what) : out_of_range(what) { } -}; - -template -class ParseTreeProperty { -public: - virtual V get(Ref node) { - return get(node.get()); - } - - virtual V get(ParseTree *const node) { - if (!debug) { - return _annotations.at(node); - } - - try { -// cerr << "node = " << node->getText() << endl; - return _annotations.at(node); - } catch (std::out_of_range &e) { - std::cerr << "get(" << node << "), text=" << node->getText() << std::endl; - std::stringstream buf; - buf << "out of range: " << node << ", text=" << node->getText(); - auto msg = buf.str(); - std::cerr << msg << std::endl; - throw MissingParseTreeProperty(msg); - } - } - - virtual void put(ParseTree *const node, V value) { - if (debug) { - std::cerr << "put(" << node << ", " << value << "), text: " << node->getText() << std::endl; - } - _annotations[node] = value; - } - - virtual V removeFrom(ParseTree *const node) { - auto it = _annotations.find(node); - - if (it == _annotations.end()) { - throw MissingParseTreeProperty(node->getText()); - } - - return it->second; - } - -protected: - std::map _annotations; - -private: -}; - -} // namespace antlr - -} diff --git a/include-priv/trygvis/string_utils.h b/include-priv/trygvis/string_utils.h deleted file mode 100644 index 9902a3f..0000000 --- a/include-priv/trygvis/string_utils.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -#include - -namespace trygvis { -namespace string_utils { - -/** - * Check if a starts with b. - */ -static bool startsWith(const std::string &a, const std::string &b) { - return b.length() <= a.length() && a.compare(0, b.length(), b) == 0; -} - -/** - * Check if a ends with b. - */ -static bool endsWith(const std::string &a, const std::string &b) { - return b.length() <= a.length() && a.compare(a.length() - b.length(), b.length(), b) == 0; -} - -} // namespace string_utils -} // namespace trygvis diff --git a/include/trygvis/kicad.h b/include/trygvis/kicad.h deleted file mode 100644 index e555e9c..0000000 --- a/include/trygvis/kicad.h +++ /dev/null @@ -1,99 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -namespace trygvis { -namespace kicad { -template -using opt = std::experimental::optional; -using std::experimental::nullopt; -} // namespace kicad -} // namespace trygvis - -namespace trygvis { -namespace kicad { -namespace netlist { - -struct lib_source { - const std::string lib; - const std::string part; - - lib_source(const std::string &lib, const std::string &part) : lib(lib), part(part) { } -}; - -struct component { - const std::string ref; - const std::string value; - const lib_source _lib_source; - - component(const std::string &ref, const std::string &value, const lib_source &_lib_source) : - ref(ref), value(value), _lib_source(_lib_source) { } -}; - -struct pin { - int num; - std::string name; - std::string type; -}; - -struct part { - std::vector pins; -}; - -class node { -public: - const std::string ref; - const int pin; - - node(const std::string &ref, int pin) : ref(ref), pin(pin) { } -}; - -class net { -public: - const int code; - const std::string name; - const std::vector nodes; - - net(int code, const std::string &name, const std::vector &nodes) : code(code), name(name), nodes(nodes) { } - - const node * node_for_ref(const std::string &ref) const; -}; - -struct netlist { - std::vector components; - std::vector parts; - std::vector nets; - - opt find_component(const std::string &ref) const; - - std::vector find_usage_of(const std::string &ref) const; -}; - -class kicad_parse_exception : public std::runtime_error { -public: - explicit kicad_parse_exception(const std::vector &messages) : - runtime_error("Parse error"), messages(messages) { } - - const std::vector messages; -}; - -class kicad_net_loader { -public: - kicad_net_loader(); - - virtual ~kicad_net_loader(); - - netlist load(std::string path); - - void setDebug(bool debug); - -private: - bool debug_; -}; - -} // namespace netlist -} // namespace kicad -} // namespace trygvis diff --git a/kicad.cmake b/kicad.cmake deleted file mode 100644 index f18e6fe..0000000 --- a/kicad.cmake +++ /dev/null @@ -1,43 +0,0 @@ -function(kicad_generate_header) - set(options IN_SOURCE) - set(oneValueArgs OUTPUT NET REF OUTPUT_DIR) - - cmake_parse_arguments(kicad_gen "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - - if (NOT kicad_gen_OUTPUT) - message(FATAL_ERROR "OUTPUT is required") - endif () - set(output "${kicad_gen_OUTPUT}") - - if (NOT kicad_gen_NET) - message(FATAL_ERROR "NET is required") - endif () - set(NET "${CMAKE_CURRENT_SOURCE_DIR}/${kicad_gen_NET}") - - if (NOT kicad_gen_REF) - message(FATAL_ERROR "REF is required") - endif () - set(REF "${kicad_gen_REF}") - - if (kicad_gen_OUTPUT_DIR) - if (kicad_gen_IN_SOURCE) - message(FATAL_ERROR "IN_SOURCE can't be used if OUTPUT_DIR is used") - endif () - - set(output_dir "${kicad_gen_OUTPUT_DIR}") - else () - if (kicad_gen_IN_SOURCE) - set(output_dir "${CMAKE_CURRENT_SOURCE_DIR}") - else () - set(output_dir "${CMAKE_CURRENT_BINARY_DIR}") - endif () - endif () - - set(output_file "${output_dir}/${output}") - - add_custom_command(OUTPUT "${output_file}" - COMMAND generate-header -n ${NET} -r ${REF} -o ${output_file} - MAIN_DEPENDENCY ${NET} - DEPENDS kicad_utils - COMMENT "Generating ${output}") -endfunction() diff --git a/kicad.cpp b/kicad.cpp deleted file mode 100644 index 0a6931d..0000000 --- a/kicad.cpp +++ /dev/null @@ -1,194 +0,0 @@ -#include "trygvis/kicad.h" -#include "trygvis/antlr.h" -#include "trygvis/string_utils.h" -#include -#include -#include -#include -#include - -using namespace std; -using namespace trygvis::antlr; -using namespace trygvis::string_utils; - -namespace trygvis { -namespace kicad { -namespace netlist { - -const node *net::node_for_ref(const std::string &ref) const { - for (auto &node: nodes) { - if (node.ref == ref) { - return &node; - } - } - - return nullptr; -} - -opt netlist::find_component(const string &ref) const { - for (const component &c :components) { - int x = c.ref.compare(ref); - if (x == 0) { - return &c; - } - } - return std::experimental::nullopt; -} - -vector netlist::find_usage_of(const string &ref) const { - vector usage; - - for (auto &net : nets) { - if (net.nodes.size() <= 1) { - continue; - } - - for (auto &node: net.nodes) { - if (node.ref == ref) { - usage.push_back(&net); - } - } - } - - return usage; -} - -static -int parse(const Ref &integer) { - unsigned long long i = strtoull(integer->getText().c_str(), NULL, 10); - - return static_cast(i); -} - -class KicadErrorListener : public BaseErrorListener { -public: - vector messages; - - void syntaxError(IRecognizer *recognizer, Token *offendingSymbol, size_t line, int charPositionInLine, - const string &msg, exception_ptr e) override { - static_cast(recognizer); - static_cast(offendingSymbol); - static_cast(e); - messages.push_back("line " + to_string(line) + ":" + to_string(charPositionInLine) + ": " + msg); - } -}; - -class kicad_main_listener : public KicadNetParserBaseListener { - -public: - vector components; - vector parts; - vector nets; - vector nodes; - - ParseTreeProperty strings; - - virtual void exitNet(KicadNetParser::NetContext *ctx) override { - auto code = parse(ctx->code()->INTEGER()); - auto name = strings.get(ctx->name()->string()); - - if (startsWith(name, "/")) { - name = name.substr(1); - } - -// cerr << "exitNet: " << "code=" << code << ", name=" << name << ", nodes=" << nodes.size() << endl; - -// if (nodes.size() > 1) { -// cerr << "Net#" << code << ": " << name << endl; -// for (auto &node: nodes) { -// cerr << " Node: " << node.ref << "#" << node.pin << endl; -// } -// } - nets.emplace_back(code, name, nodes); - nodes.clear(); - } - - virtual void exitNode(KicadNetParser::NodeContext *ctx) override { - auto ref = strings.get(ctx->ref()->string()); - auto pin = parse(ctx->pinRef()->INTEGER()); - -// cerr << "exitNode: " << "ref=" << ref << ", pin=" << pin << endl; - - nodes.emplace_back(ref, pin); - } - - virtual void exitComponent(KicadNetParser::ComponentContext *ctx) override { - auto ref = strings.get(ctx->ref()->string()); - auto value = strings.get(ctx->value()->string()); - - lib_source ls{strings.get(ctx->libsource()->lib()->string()), - strings.get(ctx->libsource()->part()->string())}; - - components.emplace_back(ref, value, ls); - } - - virtual void exitStringId(KicadNetParser::StringIdContext *ctx) override { - strings.put(ctx, ctx->getText()); - } - - virtual void exitStringInt(KicadNetParser::StringIntContext *ctx) override { - strings.put(ctx, ctx->getText()); - } - - virtual void exitStringText(KicadNetParser::StringTextContext *ctx) override { - auto s = ctx->getText(); - strings.put(ctx, s.substr(1, s.length() - 2)); - } -}; - -netlist kicad_net_loader::load(string path) { - - ANTLRFileStream input(path); - KicadNetLexer lexer(&input); - CommonTokenStream tokens(&lexer); - - tokens.fill(); - - if (debug_) { - for (auto token : tokens.getTokens()) { - cerr << token->toString() << endl; - } - } - - KicadNetParser parser(&tokens); - parser.removeErrorListeners(); - KicadErrorListener errorListener; - parser.addErrorListener(&errorListener); - - parser.file(); - - if (!errorListener.messages.empty()) { - throw kicad_parse_exception(errorListener.messages); - } - - parser.reset(); - - kicad_main_listener mainListener; - parser.addParseListener(&mainListener); - - auto file = parser.file(); - - if (debug_ && parser.getNumberOfSyntaxErrors() == 0) { - cerr << file->toStringTree(&parser) << endl; - } - - return netlist { - mainListener.components, - mainListener.parts, - mainListener.nets, - }; -} - -kicad_net_loader::kicad_net_loader() : debug_(false) { -} - -kicad_net_loader::~kicad_net_loader() { -} - -void kicad_net_loader::setDebug(bool debug) { - debug_ = debug; -} - -} // namespace netlist -} // namespace trygvis -} // namespace kicad diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt new file mode 100644 index 0000000..2b612b9 --- /dev/null +++ b/python/CMakeLists.txt @@ -0,0 +1,13 @@ +add_subdirectory(pybind11) + +pybind11_add_module(kicad_utils_py kicad_utils_py.cpp + include/trygvis/kicad/python.h + GenerateHeaderPy.cpp include/trygvis/kicad/GenerateHeaderPy.h) +pybind11_enable_warnings(kicad_utils_py) + +target_include_directories(kicad_utils_py PRIVATE include) +target_link_libraries(kicad_utils_py PRIVATE kicad-utils-core) + +install(TARGETS kicad_utils_py + EXPORT kicad_utils_export + LIBRARY DESTINATION lib) diff --git a/python/GenerateHeaderPy.cpp b/python/GenerateHeaderPy.cpp new file mode 100644 index 0000000..1a039a3 --- /dev/null +++ b/python/GenerateHeaderPy.cpp @@ -0,0 +1,40 @@ +#include "trygvis/kicad/GenerateHeaderPy.h" + +namespace trygvis { +namespace kicad { +namespace python { + +using namespace std; + +GenerateHeaderPy::GenerateHeaderPy(const string &ref, nl *netlist) : ref_(ref), netlist_(netlist) { +} + +GenerateHeaderPy::~GenerateHeaderPy() {} + +string GenerateHeaderPy::ref() { + return ref_; +} + +nl *GenerateHeaderPy::netlist() { + if (!netlist_) { + throw domain_error("No current netlist"); + } + + return netlist_; +} + +string GenerateHeaderPy::str() { + return buf_.str(); +} + +void GenerateHeaderPy::print(const string &str) { + buf_ << str; +} + +void GenerateHeaderPy::println(const string &str) { + buf_ << str << endl; +} + +} // namespace python +} // namespace kicad +} // namespace trygvis diff --git a/python/include/trygvis/kicad/GenerateHeaderPy.h b/python/include/trygvis/kicad/GenerateHeaderPy.h new file mode 100644 index 0000000..893b7ed --- /dev/null +++ b/python/include/trygvis/kicad/GenerateHeaderPy.h @@ -0,0 +1,45 @@ +#ifndef KICAD_UTILS_GENERATEHEADEPY_H +#define KICAD_UTILS_GENERATEHEADEPY_H + +#include "trygvis/kicad.h" +#include "pybind11/pybind11.h" +#include +#include + +namespace trygvis { +namespace kicad { +namespace python { + +namespace py = pybind11; + +using nl = trygvis::kicad::netlist::netlist; + +class GenerateHeaderPy { +public: + GenerateHeaderPy(const std::string &ref, nl *netlist); + + virtual ~GenerateHeaderPy(); + + std::string ref(); + + nl *netlist(); + + std::string str(); + + void print(const std::string &str); + + void println(const std::string &str); + +private: + std::string ref_; + + nl *netlist_; + + std::stringstream buf_; +}; + +} // namespace python +} // namespace kicad +} // namespace trygvis + +#endif diff --git a/python/include/trygvis/kicad/python.h b/python/include/trygvis/kicad/python.h new file mode 100644 index 0000000..f2ae816 --- /dev/null +++ b/python/include/trygvis/kicad/python.h @@ -0,0 +1,3 @@ +#pragma once + +#include "trygvis/kicad/GenerateHeaderPy.h" diff --git a/python/kicad_utils_py.cpp b/python/kicad_utils_py.cpp new file mode 100644 index 0000000..6cd3948 --- /dev/null +++ b/python/kicad_utils_py.cpp @@ -0,0 +1,38 @@ +#include "trygvis/kicad/GenerateHeaderPy.h" + +#include "pybind11/stl.h" + +using namespace std; +using namespace trygvis::kicad::netlist; +using namespace trygvis::kicad::python; + +void init_module(py::module &m) { + py::class_(m, "node"). + def_readonly("ref", &node::ref). + def_readonly("pin", &node::pin); + + py::class_(m, "net"). + def_readonly("code", &net::code). + def_readonly("name", &net::name). + def_readonly("nodes", &net::nodes). + def("node_for_ref", &net::node_for_ref, py::return_value_policy::reference); + + py::class_(m, "netlist"). + def("find_usages_of", &nl::find_usage_of, py::return_value_policy::reference); + + py::class_(m, "GenerateHeader"). + def(py::init()). + def_property_readonly("netlist", &GenerateHeaderPy::netlist). + def_property_readonly("ref", &GenerateHeaderPy::ref). + def_property_readonly("str", &GenerateHeaderPy::str). + def("print", &GenerateHeaderPy::print). + def("println", &GenerateHeaderPy::println); +} + +PYBIND11_PLUGIN(kicad_utils_py) { + py::module kicad_utils("kicad_utils_py"); + + init_module(kicad_utils); + + return kicad_utils.ptr(); +} diff --git a/python/pybind11 b/python/pybind11 new file mode 160000 index 0000000..f38f359 --- /dev/null +++ b/python/pybind11 @@ -0,0 +1 @@ +Subproject commit f38f359f96815421f1780c1a676715efd041f1ae diff --git a/template/intel-quark-d2000.py b/template/intel-quark-d2000.py new file mode 100644 index 0000000..85572c5 --- /dev/null +++ b/template/intel-quark-d2000.py @@ -0,0 +1,69 @@ +global generateHeader +gpio_map = { + 2: '10', + 3: '11', + 4: '12', + 5: '13', + 6: '14', + 7: '15', + 8: '16', + 9: '17', + 10: '18', + + 11: '9', + 13: '20', + 14: '21', + 15: '22', + 16: '23', + 18: '19', + + 21: '24', + + 31: '0', + 32: '1', + 33: '2', + 34: '3', + 35: '4', + 36: '5', + 37: '6', + 38: '7', + 39: '8' +} + +generateHeader.println(""" +#include +#include + +enum schematic_direction { + schematic_direction_out = 1, + schematic_direction_in = 2 +}; +""") + +usages = generateHeader.netlist.find_usages_of(generateHeader.ref) + +for usage in usages: + node = usage.node_for_ref(generateHeader.ref) + + gpio = gpio_map.get(node.pin) + + if gpio is None: + continue + + generateHeader.println( + 'static const uint8_t SCHEMATIC_' + usage.name + ' = ' + gpio + ';\n' + '\n' + 'static inline\n' + 'qm_rc_t schematic_' + usage.name + '_direction(enum schematic_direction dir) {\n' + ' qm_gpio_port_config_t cfg;\n' + '\n' + ' qm_gpio_get_config(QM_GPIO_0, &cfg);\n' + '\n' + ' if (dir == schematic_direction_out) {\n' + ' cfg.direction |= BIT(SCHEMATIC_' + usage.name + ');\n' + ' } else {\n' + ' cfg.direction &= ~BIT(SCHEMATIC_' + usage.name + ');\n' + ' }\n' + '\n' + ' return qm_gpio_set_config(QM_GPIO_0, &cfg);\n' + '}') -- cgit v1.2.3