From 7cb42f4368e873e5930cf84bc2494a8e8172a046 Mon Sep 17 00:00:00 2001 From: Miodrag Milanovic Date: Tue, 5 Jun 2018 21:03:06 +0200 Subject: [PATCH] Initial GUI work --- .gitignore | 2 + CMakeLists.txt | 23 +++- common/handle_error.cc | 56 ++++++++++ common/pybindings.cc | 42 ++++++++ common/pybindings.h | 3 + common/version.h.in | 6 ++ dummy/main.cc | 10 +- gui/Qt5Customizations.cmake | 60 +++++++++++ gui/emb.cc | 150 ++++++++++++++++++++++++++ gui/emb.h | 21 ++++ gui/fpgaviewwidget.cc | 153 ++++++++++++++++++++++++++ gui/fpgaviewwidget.h | 43 ++++++++ gui/gui.cmake | 26 +++++ gui/mainwindow.cc | 87 +++++++++++++++ gui/mainwindow.h | 30 ++++++ gui/mainwindow.ui | 210 ++++++++++++++++++++++++++++++++++++ gui/nextpnr.qrc | 8 ++ gui/resources/exit.png | Bin 0 -> 2360 bytes gui/resources/new.png | Bin 0 -> 1136 bytes gui/resources/open.png | Bin 0 -> 2051 bytes gui/resources/save.png | Bin 0 -> 1391 bytes ice40/main.cc | 82 ++++++++++++-- 22 files changed, 998 insertions(+), 14 deletions(-) create mode 100644 common/handle_error.cc create mode 100644 common/version.h.in create mode 100644 gui/Qt5Customizations.cmake create mode 100644 gui/emb.cc create mode 100644 gui/emb.h create mode 100644 gui/fpgaviewwidget.cc create mode 100644 gui/fpgaviewwidget.h create mode 100644 gui/gui.cmake create mode 100644 gui/mainwindow.cc create mode 100644 gui/mainwindow.h create mode 100644 gui/mainwindow.ui create mode 100644 gui/nextpnr.qrc create mode 100644 gui/resources/exit.png create mode 100644 gui/resources/new.png create mode 100644 gui/resources/open.png create mode 100644 gui/resources/save.png diff --git a/.gitignore b/.gitignore index 4f8c37ad..a36bdfc5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +/generated/ /objs/ /nextpnr-dummy /nextpnr-ice40 @@ -13,6 +14,7 @@ CMakeCache.txt *.dll *.a *.cbp +*.depends .*.swp a.out *.json diff --git a/CMakeLists.txt b/CMakeLists.txt index cd559203..db866b5f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,13 +7,26 @@ set(CMAKE_CXX_STANDARD 11) # set(CMAKE_CXX_FLAGS "-Wall -pedantic -Wextra -Werror") set(CMAKE_DEFIN) # List of Boost libraries to include -set(boost_libs filesystem thread) +set(boost_libs filesystem thread program_options) # TODO: sensible minimum Python version find_package(PythonInterp 3.5 REQUIRED) find_package(PythonLibs 3.5 REQUIRED) find_package(Boost REQUIRED COMPONENTS ${boost_libs}) +# Get the latest abbreviated commit hash of the working branch +execute_process( + COMMAND git log -1 --format=%h + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE GIT_COMMIT_HASH + OUTPUT_STRIP_TRAILING_WHITESPACE +) + +add_definitions("-DGIT_COMMIT_HASH=${GIT_COMMIT_HASH}") +configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/common/version.h.in ${CMAKE_CURRENT_BINARY_DIR}/generated/version.h +) + # Find Boost::Python of a suitable version in a cross-platform way # Some distributions (Arch) call it libboost_python3, others such as Ubuntu # call it libboost_python35. In the latter case we must consider all minor versions @@ -41,16 +54,16 @@ if (NOT Boost_PYTHON_FOUND) find_package(Boost COMPONENTS python3 ${boost_libs}) endif () -# TODO: Find and include Qt +include(gui/gui.cmake) -include_directories(common/ ${Boost_INCLUDE_DIRS} ${PYTHON_INCLUDE_DIRS}) +include_directories(common/ gui/ ${Boost_INCLUDE_DIRS} ${PYTHON_INCLUDE_DIRS}) aux_source_directory(common/ COMMON_FILES) foreach (family ${FAMILIES}) string(TOUPPER ${family} ufamily) aux_source_directory(${family}/ ${ufamily}_FILES) # Add the CLI binary target - add_executable(nextpnr-${family} ${COMMON_FILES} ${${ufamily}_FILES}) + add_executable(nextpnr-${family} ${COMMON_FILES} ${${ufamily}_FILES} ${GUI_SOURCE_FILES}) # Add the importable Python module target PYTHON_ADD_MODULE(nextpnrpy_${family} ${COMMON_FILES} ${${ufamily}_FILES}) # Add any new per-architecture targets here @@ -63,7 +76,7 @@ foreach (family ${FAMILIES}) # Include family-specific source files to all family targets and set defines appropriately target_include_directories(${target} PRIVATE ${family}/) target_compile_definitions(${target} PRIVATE ARCH_${ufamily} ARCHNAME=${family}) - target_link_libraries(${target} LINK_PUBLIC ${Boost_LIBRARIES} ${PYTHON_LIBRARIES}) + target_link_libraries(${target} LINK_PUBLIC ${Boost_LIBRARIES} ${PYTHON_LIBRARIES} ${GUI_LIBRARY_FILES}) endforeach (target) endforeach (family) diff --git a/common/handle_error.cc b/common/handle_error.cc new file mode 100644 index 00000000..a06b5348 --- /dev/null +++ b/common/handle_error.cc @@ -0,0 +1,56 @@ +#include +#include + +namespace py = boost::python; + +// Parses the value of the active python exception +// NOTE SHOULD NOT BE CALLED IF NO EXCEPTION +std::string parse_python_exception(){ + PyObject *type_ptr = NULL, *value_ptr = NULL, *traceback_ptr = NULL; + // Fetch the exception info from the Python C API + PyErr_Fetch(&type_ptr, &value_ptr, &traceback_ptr); + + // Fallback error + std::string ret("Unfetchable Python error"); + // If the fetch got a type pointer, parse the type into the exception string + if(type_ptr != NULL){ + py::handle<> h_type(type_ptr); + py::str type_pstr(h_type); + // Extract the string from the boost::python object + py::extract e_type_pstr(type_pstr); + // If a valid string extraction is available, use it + // otherwise use fallback + if(e_type_pstr.check()) + ret = e_type_pstr(); + else + ret = "Unknown exception type"; + } + // Do the same for the exception value (the stringification of the exception) + if(value_ptr != NULL){ + py::handle<> h_val(value_ptr); + py::str a(h_val); + py::extract returned(a); + if(returned.check()) + ret += ": " + returned(); + else + ret += std::string(": Unparseable Python error: "); + } + // Parse lines from the traceback using the Python traceback module + if(traceback_ptr != NULL){ + py::handle<> h_tb(traceback_ptr); + // Load the traceback module and the format_tb function + py::object tb(py::import("traceback")); + py::object fmt_tb(tb.attr("format_tb")); + // Call format_tb to get a list of traceback strings + py::object tb_list(fmt_tb(h_tb)); + // Join the traceback strings into a single string + py::object tb_str(py::str("\n").join(tb_list)); + // Extract the string, check the extraction, and fallback in necessary + py::extract returned(tb_str); + if(returned.check()) + ret += ": " + returned(); + else + ret += std::string(": Unparseable Python traceback"); + } + return ret; +} diff --git a/common/pybindings.cc b/common/pybindings.cc index 8bae4e51..4986c549 100644 --- a/common/pybindings.cc +++ b/common/pybindings.cc @@ -18,6 +18,7 @@ * */ + #include "design.h" #include "chip.h" #include "pybindings.h" @@ -26,6 +27,9 @@ #define PASTER(x, y) x ## _ ## y #define EVALUATOR(x, y) PASTER(x,y) #define MODULE_NAME EVALUATOR(nextpnrpy, ARCHNAME) +#define PYINIT_MODULE_NAME EVALUATOR(&PyInit_nextpnrpy, ARCHNAME) +#define STRINGIFY(x) #x +#define TOSTRING(x) STRINGIFY(x) // Architecture-specific bindings should be created in the below function, which must be implemented in all // architectures @@ -39,3 +43,41 @@ BOOST_PYTHON_MODULE (MODULE_NAME) { arch_wrap_python(); } + +void arch_appendinittab() +{ + PyImport_AppendInittab(TOSTRING(MODULE_NAME), PYINIT_MODULE_NAME); +} + +void execute_python_file(const char *executable, const char* python_file) +{ + wchar_t *program = Py_DecodeLocale(executable, NULL); + if (program == NULL) { + fprintf(stderr, "Fatal error: cannot decode executable filename\n"); + exit(1); + } + try + { + PyImport_AppendInittab(TOSTRING(MODULE_NAME), PYINIT_MODULE_NAME); + Py_SetProgramName(program); + Py_Initialize(); + + FILE* fp = fopen(python_file, "r"); + if (fp == NULL) { + fprintf(stderr, "Fatal error: file not found %s\n",python_file); + exit(1); + } + PyRun_SimpleFile(fp , python_file); + fclose(fp); + + Py_Finalize(); + PyMem_RawFree(program); + } + catch(boost::python::error_already_set const &) + { + // Parse and output the exception + std::string perror_str = parse_python_exception(); + std::cout << "Error in Python: " << perror_str << std::endl; + } +} + diff --git a/common/pybindings.h b/common/pybindings.h index eab2039d..f594784c 100644 --- a/common/pybindings.h +++ b/common/pybindings.h @@ -79,5 +79,8 @@ struct range_wrapper { #define WRAP_RANGE(t) range_wrapper().wrap(#t "Range", #t "Iterator") +void execute_python_file(const char *executable, const char* python_file); +std::string parse_python_exception(); +void arch_appendinittab(); #endif /* end of include guard: COMMON_PYBINDINGS_HH */ diff --git a/common/version.h.in b/common/version.h.in new file mode 100644 index 00000000..8072116b --- /dev/null +++ b/common/version.h.in @@ -0,0 +1,6 @@ +#ifndef VERSION_H +#define VERSION_H + +#define GIT_COMMIT_HASH_STR "@GIT_COMMIT_HASH@" + +#endif \ No newline at end of file diff --git a/dummy/main.cc b/dummy/main.cc index e3a784ca..be4b47a9 100644 --- a/dummy/main.cc +++ b/dummy/main.cc @@ -18,13 +18,19 @@ */ #include "design.h" +#include "mainwindow.h" +#include -int main() +int main(int argc, char *argv[]) { Design design(ChipArgs{}); for (auto bel : design.chip.getBels()) printf("%s\n", design.chip.getBelName(bel).c_str()); - return 0; + QApplication a(argc, argv); + MainWindow w; + w.show(); + + return a.exec(); } diff --git a/gui/Qt5Customizations.cmake b/gui/Qt5Customizations.cmake new file mode 100644 index 00000000..39d3a644 --- /dev/null +++ b/gui/Qt5Customizations.cmake @@ -0,0 +1,60 @@ +include(CMakeParseArguments) + +# qt5_wrap_ui_custom(outfiles inputfile ... ) + +function(QT5_WRAP_UI_CUSTOM outfiles ) + set(options) + set(oneValueArgs) + set(multiValueArgs OPTIONS) + + cmake_parse_arguments(_WRAP_UI "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + set(ui_files ${_WRAP_UI_UNPARSED_ARGUMENTS}) + set(ui_options ${_WRAP_UI_OPTIONS}) + + foreach(it ${ui_files}) + get_filename_component(outfile ${it} NAME_WE) + get_filename_component(infile ${it} ABSOLUTE) + set(outfile ${CMAKE_CURRENT_BINARY_DIR}/generated/ui_${outfile}.h) + add_custom_command(OUTPUT ${outfile} + COMMAND ${Qt5Widgets_UIC_EXECUTABLE} + ARGS ${ui_options} -o ${outfile} ${infile} + MAIN_DEPENDENCY ${infile} VERBATIM) + list(APPEND ${outfiles} ${outfile}) + endforeach() + set(${outfiles} ${${outfiles}} PARENT_SCOPE) +endfunction() + +# qt5_add_resources_custom(outfiles inputfile ... ) + +function(QT5_ADD_RESOURCES_CUSTOM outfiles ) + + set(options) + set(oneValueArgs) + set(multiValueArgs OPTIONS) + + cmake_parse_arguments(_RCC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + set(rcc_files ${_RCC_UNPARSED_ARGUMENTS}) + set(rcc_options ${_RCC_OPTIONS}) + + if("${rcc_options}" MATCHES "-binary") + message(WARNING "Use qt5_add_binary_resources for binary option") + endif() + + foreach(it ${rcc_files}) + get_filename_component(outfilename ${it} NAME_WE) + get_filename_component(infile ${it} ABSOLUTE) + set(outfile ${CMAKE_CURRENT_BINARY_DIR}/generated/qrc_${outfilename}.cc) + + _QT5_PARSE_QRC_FILE(${infile} _out_depends _rc_depends) + + add_custom_command(OUTPUT ${outfile} + COMMAND ${Qt5Core_RCC_EXECUTABLE} + ARGS ${rcc_options} --name ${outfilename} --output ${outfile} ${infile} + MAIN_DEPENDENCY ${infile} + DEPENDS ${_rc_depends} "${out_depends}" VERBATIM) + list(APPEND ${outfiles} ${outfile}) + endforeach() + set(${outfiles} ${${outfiles}} PARENT_SCOPE) +endfunction() \ No newline at end of file diff --git a/gui/emb.cc b/gui/emb.cc new file mode 100644 index 00000000..59915111 --- /dev/null +++ b/gui/emb.cc @@ -0,0 +1,150 @@ +// +// Copyright (C) 2011 Mateusz Loskot +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +// Blog article: http://mateusz.loskot.net/?p=2819 + +#include +#include +#include +#include +#include "emb.h" + +namespace emb +{ +struct Stdout +{ + PyObject_HEAD + stdout_write_type write; +}; + +PyObject* Stdout_write(PyObject* self, PyObject* args) +{ + std::size_t written(0); + Stdout* selfimpl = reinterpret_cast(self); + if (selfimpl->write) + { + char* data; + if (!PyArg_ParseTuple(args, "s", &data)) + return 0; + + std::string str(data); + selfimpl->write(str); + written = str.size(); + } + return PyLong_FromSize_t(written); +} + +PyObject* Stdout_flush(PyObject* self, PyObject* args) +{ + // no-op + return Py_BuildValue(""); +} + +PyMethodDef Stdout_methods[] = +{ + {"write", Stdout_write, METH_VARARGS, "sys.stdout.write"}, + {"flush", Stdout_flush, METH_VARARGS, "sys.stdout.write"}, + {0, 0, 0, 0} // sentinel +}; + +PyTypeObject StdoutType = +{ + PyVarObject_HEAD_INIT(0, 0) + "emb.StdoutType", /* tp_name */ + sizeof(Stdout), /* tp_basicsize */ + 0, /* tp_itemsize */ + 0, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_reserved */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + "emb.Stdout objects", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + Stdout_methods, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + 0, /* tp_new */ +}; + +PyModuleDef embmodule = +{ + PyModuleDef_HEAD_INIT, + "emb", 0, -1, 0, +}; + +// Internal state +PyObject* g_stdout; +PyObject* g_stdout_saved; + +PyMODINIT_FUNC PyInit_emb(void) +{ + g_stdout = 0; + g_stdout_saved = 0; + + StdoutType.tp_new = PyType_GenericNew; + if (PyType_Ready(&StdoutType) < 0) + return 0; + + PyObject* m = PyModule_Create(&embmodule); + if (m) + { + Py_INCREF(&StdoutType); + PyModule_AddObject(m, "Stdout", reinterpret_cast(&StdoutType)); + } + return m; +} + +void set_stdout(stdout_write_type write) +{ + if (!g_stdout) + { + g_stdout_saved = PySys_GetObject("stdout"); // borrowed + g_stdout = StdoutType.tp_new(&StdoutType, 0, 0); + } + + Stdout* impl = reinterpret_cast(g_stdout); + impl->write = write; + PySys_SetObject("stdout", g_stdout); +} + +void reset_stdout() +{ + if (g_stdout_saved) + PySys_SetObject("stdout", g_stdout_saved); + + Py_XDECREF(g_stdout); + g_stdout = 0; +} + +void append_inittab() +{ + PyImport_AppendInittab("emb", emb::PyInit_emb); +} + +} // namespace emb diff --git a/gui/emb.h b/gui/emb.h new file mode 100644 index 00000000..f76588f5 --- /dev/null +++ b/gui/emb.h @@ -0,0 +1,21 @@ +// +// Copyright (C) 2011 Mateusz Loskot +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +// Blog article: http://mateusz.loskot.net/?p=2819 + +#include +#include +#include + +namespace emb +{ + typedef std::function stdout_write_type; + + void set_stdout(stdout_write_type write); + void reset_stdout(); + + void append_inittab(); +} // namespace emb diff --git a/gui/fpgaviewwidget.cc b/gui/fpgaviewwidget.cc new file mode 100644 index 00000000..bd36b724 --- /dev/null +++ b/gui/fpgaviewwidget.cc @@ -0,0 +1,153 @@ +#include +#include +#include +#include "fpgaviewwidget.h" + +FPGAViewWidget::FPGAViewWidget(QWidget *parent) + : QOpenGLWidget(parent), + m_xMove(0),m_yMove(0),m_zDistance(1.0) +{ + +} + +FPGAViewWidget::~FPGAViewWidget() +{ +} + +QSize FPGAViewWidget::minimumSizeHint() const +{ + return QSize(640, 480); +} + +QSize FPGAViewWidget::sizeHint() const +{ + return QSize(640, 480); +} + +void FPGAViewWidget::setXTranslation(float t_x) +{ + if(t_x != m_xMove) + { + m_xMove = t_x; + update(); + } +} + + +void FPGAViewWidget::setYTranslation(float t_y) +{ + if(t_y != m_yMove) + { + m_yMove = t_y; + update(); + } +} + + +void FPGAViewWidget::setZoom(float t_z) +{ + if(t_z != m_zDistance) + { + m_zDistance -= t_z; + if(m_zDistance < 0.1f) + m_zDistance = 0.1f; + if(m_zDistance > 10.0f) + m_zDistance = 10.0f; + + update(); + } +} + +void FPGAViewWidget::initializeGL() +{ + initializeOpenGLFunctions(); + glClearColor(1.0, 1.0, 1.0, 0.0); +} + +void FPGAViewWidget::paintGL() +{ + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glLoadIdentity(); + + glTranslatef(m_xMove, m_yMove, -10.0); + glScalef(m_zDistance,m_zDistance, 0.0f); + + // Example grid + glColor3f(0.8, 0.8, 0.8); + glBegin(GL_LINES); + for(float i = -100; i <= 100; i += 0.1) + { + glVertex3f((float)i, -100.0f, 0.0f); + glVertex3f((float)i, 100.0f, 0.0f); + glVertex3f(-100.0f, (float)i, 0.0f); + glVertex3f(100.0f, (float)i, 0.0f); + } + glColor3f(0.5, 0.5, 0.5); + for(int i = -100; i <= 100; i += 1) + { + glVertex3f((float)i, -100.0f, 0.0f); + glVertex3f((float)i, 100.0f, 0.0f); + glVertex3f(-100.0f, (float)i, 0.0f); + glVertex3f(100.0f, (float)i, 0.0f); + } + glEnd(); + + // Example triangle + glBegin(GL_TRIANGLES); + glColor3f(1.0, 0.0, 0.0); + glVertex3f(-0.5, -0.5, 0); + glColor3f(0.0, 1.0, 0.0); + glVertex3f(0.5, -0.5, 0); + glColor3f(0.0, 0.0, 1.0); + glVertex3f(0, 0.5, 0); + glEnd(); + +} + +void FPGAViewWidget::resizeGL(int width, int height) +{ + m_windowWidth = width; + m_windowHeight = height; + glViewport(0, 0, m_windowWidth, m_windowHeight); + + float aspect = width * 1.0 / height; + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(-1.0* aspect, +1.0* aspect, -1.0, +1.0, 1.0, 15.0); + glMatrixMode(GL_MODELVIEW); +} + +void FPGAViewWidget::mousePressEvent(QMouseEvent *event) +{ + m_lastPos = event->pos(); +} + +void FPGAViewWidget::mouseMoveEvent(QMouseEvent *event) +{ + int dx = event->x() - m_lastPos.x(); + int dy = event->y() - m_lastPos.y(); + float dx_scale = dx * (1 / (float)640); + float dy_scale = -dy * (1 / (float)480); + + if (event->buttons() & Qt::LeftButton) + { + float xpos = m_xMove + dx_scale; + float ypos = m_yMove + dy_scale; + if (m_xMove/m_zDistance <= 100.0 && m_xMove/m_zDistance>= -100.0) setXTranslation(xpos); + if (m_yMove/m_zDistance <= 100.0 && m_yMove/m_zDistance>= -100.0) setYTranslation(ypos); + + } + m_lastPos = event->pos(); +} + +void FPGAViewWidget::wheelEvent(QWheelEvent *event) +{ + QPoint degree = event->angleDelta() / 8; + + if(!degree.isNull()) + { + QPoint step = degree / 15; + setZoom(step.y() * -0.1f); + } +} diff --git a/gui/fpgaviewwidget.h b/gui/fpgaviewwidget.h new file mode 100644 index 00000000..c222e405 --- /dev/null +++ b/gui/fpgaviewwidget.h @@ -0,0 +1,43 @@ +#ifndef MAPGLWIDGET_H +#define MAPGLWIDGET_H + +#include +#include +#include + +class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions +{ + Q_OBJECT + +public: + FPGAViewWidget(QWidget *parent = 0); + ~FPGAViewWidget(); + + QSize minimumSizeHint() const override; + QSize sizeHint() const override; + + void setXTranslation(float t_x); + void setYTranslation(float t_y); + void setZoom(float t_z); + + void xRotationChanged(int angle); + void yRotationChanged(int angle); + void zRotationChanged(int angle); + +protected: + void initializeGL() Q_DECL_OVERRIDE; + void paintGL() Q_DECL_OVERRIDE; + void resizeGL(int width, int height) Q_DECL_OVERRIDE; + void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE; + void mouseMoveEvent(QMouseEvent *event) Q_DECL_OVERRIDE; + void wheelEvent(QWheelEvent *event) Q_DECL_OVERRIDE; + +private: + int m_windowWidth; + int m_windowHeight; + float m_xMove; + float m_yMove; + float m_zDistance; + QPoint m_lastPos; +}; +#endif diff --git a/gui/gui.cmake b/gui/gui.cmake new file mode 100644 index 00000000..6fe1e4ac --- /dev/null +++ b/gui/gui.cmake @@ -0,0 +1,26 @@ +# Find the Qt5 libraries +find_package(Qt5 COMPONENTS Core Widgets OpenGL REQUIRED) + +ADD_DEFINITIONS(-DQT_NO_KEYWORDS) + +include( gui/Qt5Customizations.cmake ) + +# Find includes in corresponding build directories +include_directories(${CMAKE_CURRENT_BINARY_DIR}/generated) + +qt5_generate_moc(gui/mainwindow.h ${CMAKE_CURRENT_BINARY_DIR}/generated/moc_mainwindow.cc) +qt5_generate_moc(gui/fpgaviewwidget.h ${CMAKE_CURRENT_BINARY_DIR}/generated/moc_fpgaviewwidget.cc) + +set(GENERATED_MOC_FILES + ${CMAKE_CURRENT_BINARY_DIR}/generated/moc_mainwindow.cc + ${CMAKE_CURRENT_BINARY_DIR}/generated/moc_fpgaviewwidget.cc +) + +set(UI_SOURCES + gui/mainwindow.ui +) +qt5_wrap_ui_custom(GENERATED_UI_HEADERS ${UI_SOURCES}) +qt5_add_resources_custom(GUI_RESOURCE_FILES gui/nextpnr.qrc) + +set(GUI_SOURCE_FILES gui/mainwindow.cc gui/fpgaviewwidget.cc gui/emb.cc ${GENERATED_MOC_FILES} ${GENERATED_UI_HEADERS} ${GUI_RESOURCE_FILES}) +set(GUI_LIBRARY_FILES Qt5::Widgets Qt5::OpenGL GL) \ No newline at end of file diff --git a/gui/mainwindow.cc b/gui/mainwindow.cc new file mode 100644 index 00000000..4404fc5a --- /dev/null +++ b/gui/mainwindow.cc @@ -0,0 +1,87 @@ +#include "mainwindow.h" +#include "ui_mainwindow.h" +#include +#include +#include +#include "pybindings.h" +#include "emb.h" + +MainWindow::MainWindow(QWidget *parent) : + QMainWindow(parent), + ui(new Ui::MainWindow) +{ + ui->setupUi(this); + emb::append_inittab(); + arch_appendinittab(); + Py_Initialize(); + PyImport_ImportModule("emb"); + + write = [this] (std::string s) { + //ui->plainTextEdit->moveCursor(QTextCursor::End); + //ui->plainTextEdit->insertPlainText(s.c_str()); + //ui->plainTextEdit->moveCursor(QTextCursor::End); + ui->plainTextEdit->appendPlainText(s.c_str()); + }; + emb::set_stdout(write); +} + +MainWindow::~MainWindow() +{ + Py_Finalize(); + delete ui; +} + +void handle_system_exit() +{ + exit(-1); +} + +int MainWindow::executePython(std::string command) +{ + PyObject *m, *d, *v; + m = PyImport_AddModule("__main__"); + if (m == NULL) + return -1; + d = PyModule_GetDict(m); + v = PyRun_StringFlags(command.c_str(), (command.empty() ? Py_file_input : Py_single_input), d, d, NULL); + if (v == NULL) + { + PyObject *exception, *v, *tb; + + if (PyErr_ExceptionMatches(PyExc_SystemExit)) { + handle_system_exit(); + } + PyErr_Fetch(&exception, &v, &tb); + if (exception == NULL) + return 0; + PyErr_NormalizeException(&exception, &v, &tb); + if (tb == NULL) { + tb = Py_None; + Py_INCREF(tb); + } + PyException_SetTraceback(v, tb); + if (exception == NULL) + return 0; + PyErr_Clear(); + + PyObject* objectsRepresentation = PyObject_Str(v); + const char* errorStr = PyUnicode_AsUTF8(objectsRepresentation); + ui->plainTextEdit->appendPlainText(errorStr); + Py_DECREF(objectsRepresentation); + Py_XDECREF(exception); + Py_XDECREF(v); + Py_XDECREF(tb); + return -1; + } + Py_DECREF(v); + return 0; +} + +void MainWindow::on_lineEdit_returnPressed() +{ + std::string input = ui->lineEdit->text().toStdString(); + ui->plainTextEdit->appendPlainText(std::string(">>> " + input).c_str()); + ui->plainTextEdit->update(); + ui->lineEdit->clear(); + int error = executePython(input); +} diff --git a/gui/mainwindow.h b/gui/mainwindow.h new file mode 100644 index 00000000..5b242356 --- /dev/null +++ b/gui/mainwindow.h @@ -0,0 +1,30 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H +#include "emb.h" + +#include + +namespace Ui { +class MainWindow; +} + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = 0); + ~MainWindow(); + +private: + int executePython(std::string command); + +private Q_SLOTS: + void on_lineEdit_returnPressed(); + +private: + Ui::MainWindow *ui; + emb::stdout_write_type write; +}; + +#endif // MAINWINDOW_H diff --git a/gui/mainwindow.ui b/gui/mainwindow.ui new file mode 100644 index 00000000..4e236f3c --- /dev/null +++ b/gui/mainwindow.ui @@ -0,0 +1,210 @@ + + + MainWindow + + + + 0 + 0 + 969 + 629 + + + + nextpnr + + + + + + + Qt::Horizontal + + + + Qt::Vertical + + + + + true + + + + 0 + 50 + + + + + 16777215 + 200 + + + + true + + + + + + 0 + 30 + + + + + 16777215 + 30 + + + + + + + Qt::Vertical + + + + + 200 + 0 + + + + + 200 + 16777215 + + + + + + + + + + + + + 0 + 0 + 969 + 27 + + + + + &File + + + + + + + + + + + + &Help + + + + + + + + + TopToolBarArea + + + false + + + + + + + + + + :/icons/resources/new.png:/icons/resources/new.png + + + New + + + + + + :/icons/resources/open.png:/icons/resources/open.png + + + Open + + + + + + :/icons/resources/save.png:/icons/resources/save.png + + + Save + + + + + Save as ... + + + + + Close + + + + + + :/icons/resources/exit.png:/icons/resources/exit.png + + + Exit + + + + + About + + + + + + + FPGAViewWidget + QWidget +
fpgaviewwidget.h
+
+
+ + + + + + actionExit + triggered() + MainWindow + close() + + + -1 + -1 + + + 484 + 314 + + + + +
diff --git a/gui/nextpnr.qrc b/gui/nextpnr.qrc new file mode 100644 index 00000000..b9e2f237 --- /dev/null +++ b/gui/nextpnr.qrc @@ -0,0 +1,8 @@ + + + resources/new.png + resources/open.png + resources/save.png + resources/exit.png + + diff --git a/gui/resources/exit.png b/gui/resources/exit.png new file mode 100644 index 0000000000000000000000000000000000000000..b1329cbc8296ba6b28ca85b373680931dde1c366 GIT binary patch literal 2360 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 literal 0 HcmV?d00001 diff --git a/gui/resources/save.png b/gui/resources/save.png new file mode 100644 index 0000000000000000000000000000000000000000..1732110995386b7ae6d98a74408a38e47ced358a GIT binary patch literal 1391 zcmV-#1(5oQP)$gWfB^I$cBlj}XxErw0iYw44sC61&=(3p zS9dp5R#qy2LuVG+Op?*Tp3{X_FC08YCxaet{DXXtW}L3tyxMp$e-U^D)`8k>0)Tnt zMGw(a^jhaI1~qON2*&_IeSOf`(FK8k1+d=#I>=bIp8Os5>^&I{wIB0e>-xF{O=Yd# zeSNckrSIVtSq^y`KNJAexYw&$0MwwtN? z@sj=L;Qq{HOdO^_#Y@djfIYQED#pPxy$XPc@@cXJBzztH>K0-6vJ%ww$|y0Ni7qK&B}T3(u8dW{B7n080S@m7B%^?p~OP z=Vwy|DqlP71laLTA$mDRceMT%5#X#Y!rm@*{70Ba~eyDuQ3z$>09!j?Th0q&YV z3juP|=i;8MBp+KLmI~nG%yrKfRQj6pY=8)j;t5nBZO%Kr}O)6;{b{A z2G+WQMG3J1zU>%5iH2w*GJxSR0%tq>RU%61qf?MR3VfWIng9g?AU;k<1s=#qj16$& zVm~ej)2|2v7#cPCL6a1mh)Wf_^lTmDSuOx(>0Ug4Rd!Nr0QUaNZ#)C|hXMSDf+L^H z^+Y^du_z7!XbOO~Y}(g|>rA$b{{r}$E|h^%BRl{A002ovPDHLkV1fmjhAaR8 literal 0 HcmV?d00001 diff --git a/ice40/main.cc b/ice40/main.cc index 1a63f003..e02930bb 100644 --- a/ice40/main.cc +++ b/ice40/main.cc @@ -16,18 +16,86 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * */ - #include "design.h" +#include "mainwindow.h" +#include +#include +#include "version.h" +#include +#include "pybindings.h" -int main() +int main(int argc, char *argv[]) { - ChipArgs chipArgs; - chipArgs.type = ChipArgs::LP384; + namespace po = boost::program_options; - Design design(chipArgs); + std::string str; - for (auto bel : design.chip.getBels()) - printf("%s\n", design.chip.getBelName(bel).c_str()); + po::options_description options("Allowed options"); + options.add_options()("help,h","show help"); + options.add_options()("debug","just a check"); + options.add_options()("gui","start gui"); + options.add_options()("file", po::value(), "python file to execute"); + options.add_options()("version,v","show version"); + po::positional_options_description pos; + pos.add("file", -1); + + po::variables_map vm; + try { + po::parsed_options parsed = po::command_line_parser(argc, argv). + options(options). + positional(pos). + run(); + + po::store(parsed, vm); + + po::notify(vm); + } + catch(std::exception& e) + { + std::cout << e.what() << "\n"; + return 1; + } + + if (vm.count("help") || argc == 1) + { + std::cout << basename(argv[0]) << " -- Next Generation Place and Route (git sha1 " GIT_COMMIT_HASH_STR ")\n"; + std::cout << "\n"; + std::cout << options << "\n"; + return 1; + } + + if (vm.count("version")) + { + std::cout << basename(argv[0]) << " -- Next Generation Place and Route (git sha1 " GIT_COMMIT_HASH_STR ")\n"; + return 1; + } + + if (vm.count("gui")) + { + QApplication a(argc, argv); + MainWindow w; + w.show(); + + return a.exec(); + } + + if (vm.count("debug")) + { + ChipArgs chipArgs; + chipArgs.type = ChipArgs::LP384; + + Design design(chipArgs); + for (auto bel : design.chip.getBels()) + printf("%s\n", design.chip.getBelName(bel).c_str()); + return 0; + } + + if (vm.count("file")) + { + std::string filename = vm["file"].as(); + execute_python_file(argv[0],filename.c_str()); + } + return 0; }