aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMiodrag Milanovic <mmicko@gmail.com>2018-06-05 21:03:06 +0200
committerMiodrag Milanovic <mmicko@gmail.com>2018-06-05 21:03:06 +0200
commit7cb42f4368e873e5930cf84bc2494a8e8172a046 (patch)
treeed773278240546b1b525c066b95a4e69150aabf1
parentbd08f9e698269764d9e06052470d4a699f69f951 (diff)
downloadnextpnr-7cb42f4368e873e5930cf84bc2494a8e8172a046.tar.gz
nextpnr-7cb42f4368e873e5930cf84bc2494a8e8172a046.tar.bz2
nextpnr-7cb42f4368e873e5930cf84bc2494a8e8172a046.zip
Initial GUI work
-rw-r--r--.gitignore2
-rw-r--r--CMakeLists.txt23
-rw-r--r--common/handle_error.cc56
-rw-r--r--common/pybindings.cc42
-rw-r--r--common/pybindings.h3
-rw-r--r--common/version.h.in6
-rw-r--r--dummy/main.cc10
-rw-r--r--gui/Qt5Customizations.cmake60
-rw-r--r--gui/emb.cc150
-rw-r--r--gui/emb.h21
-rw-r--r--gui/fpgaviewwidget.cc153
-rw-r--r--gui/fpgaviewwidget.h43
-rw-r--r--gui/gui.cmake26
-rw-r--r--gui/mainwindow.cc87
-rw-r--r--gui/mainwindow.h30
-rw-r--r--gui/mainwindow.ui210
-rw-r--r--gui/nextpnr.qrc8
-rw-r--r--gui/resources/exit.pngbin0 -> 2360 bytes
-rw-r--r--gui/resources/new.pngbin0 -> 1136 bytes
-rw-r--r--gui/resources/open.pngbin0 -> 2051 bytes
-rw-r--r--gui/resources/save.pngbin0 -> 1391 bytes
-rw-r--r--ice40/main.cc82
22 files changed, 998 insertions, 14 deletions
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 <boost/python.hpp>
+#include <Python.h>
+
+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<std::string> 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<std::string> 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<std::string> 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<t##Range>().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 <QApplication>
-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 <mateusz@loskot.net>
+// 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 <functional>
+#include <iostream>
+#include <string>
+#include <Python.h>
+#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<Stdout*>(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<PyObject*>(&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<Stdout*>(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 <mateusz@loskot.net>
+// 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 <functional>
+#include <iostream>
+#include <string>
+
+namespace emb
+{
+ typedef std::function<void(std::string)> 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 <QMouseEvent>
+#include <QCoreApplication>
+#include <math.h>
+#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 <QOpenGLWidget>
+#include <QOpenGLFunctions>
+#include <QPainter>
+
+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 <functional>
+#include <iostream>
+#include <string>
+#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 <QMainWindow>
+
+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 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>969</width>
+ <height>629</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>nextpnr</string>
+ </property>
+ <widget class="QWidget" name="centralWidget">
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0">
+ <widget class="QSplitter" name="splitter_3">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <widget class="QSplitter" name="splitter">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <widget class="FPGAViewWidget" name="openGLWidget" native="true"/>
+ <widget class="QPlainTextEdit" name="plainTextEdit">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>50</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>16777215</width>
+ <height>200</height>
+ </size>
+ </property>
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ </widget>
+ <widget class="QLineEdit" name="lineEdit">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>30</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>16777215</width>
+ <height>30</height>
+ </size>
+ </property>
+ </widget>
+ </widget>
+ <widget class="QSplitter" name="splitter_2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <widget class="QTreeView" name="treeView">
+ <property name="minimumSize">
+ <size>
+ <width>200</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>200</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ </widget>
+ <widget class="QTableView" name="tableView"/>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QMenuBar" name="menuBar">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>969</width>
+ <height>27</height>
+ </rect>
+ </property>
+ <widget class="QMenu" name="menu_File">
+ <property name="title">
+ <string>&amp;File</string>
+ </property>
+ <addaction name="actionNew"/>
+ <addaction name="actionOpen"/>
+ <addaction name="actionSave"/>
+ <addaction name="actionSave_as"/>
+ <addaction name="actionClose"/>
+ <addaction name="separator"/>
+ <addaction name="actionExit"/>
+ </widget>
+ <widget class="QMenu" name="menu_Help">
+ <property name="title">
+ <string>&amp;Help</string>
+ </property>
+ <addaction name="actionAbout"/>
+ </widget>
+ <addaction name="menu_File"/>
+ <addaction name="menu_Help"/>
+ </widget>
+ <widget class="QToolBar" name="mainToolBar">
+ <attribute name="toolBarArea">
+ <enum>TopToolBarArea</enum>
+ </attribute>
+ <attribute name="toolBarBreak">
+ <bool>false</bool>
+ </attribute>
+ <addaction name="actionNew"/>
+ <addaction name="actionOpen"/>
+ <addaction name="actionSave"/>
+ </widget>
+ <widget class="QStatusBar" name="statusBar"/>
+ <action name="actionNew">
+ <property name="icon">
+ <iconset resource="nextpnr.qrc">
+ <normaloff>:/icons/resources/new.png</normaloff>:/icons/resources/new.png</iconset>
+ </property>
+ <property name="text">
+ <string>New</string>
+ </property>
+ </action>
+ <action name="actionOpen">
+ <property name="icon">
+ <iconset resource="nextpnr.qrc">
+ <normaloff>:/icons/resources/open.png</normaloff>:/icons/resources/open.png</iconset>
+ </property>
+ <property name="text">
+ <string>Open</string>
+ </property>
+ </action>
+ <action name="actionSave">
+ <property name="icon">
+ <iconset resource="nextpnr.qrc">
+ <normaloff>:/icons/resources/save.png</normaloff>:/icons/resources/save.png</iconset>
+ </property>
+ <property name="text">
+ <string>Save</string>
+ </property>
+ </action>
+ <action name="actionSave_as">
+ <property name="text">
+ <string>Save as ...</string>
+ </property>
+ </action>
+ <action name="actionClose">
+ <property name="text">
+ <string>Close</string>
+ </property>
+ </action>
+ <action name="actionExit">
+ <property name="icon">
+ <iconset resource="nextpnr.qrc">
+ <normaloff>:/icons/resources/exit.png</normaloff>:/icons/resources/exit.png</iconset>
+ </property>
+ <property name="text">
+ <string>Exit</string>
+ </property>
+ </action>
+ <action name="actionAbout">
+ <property name="text">
+ <string>About</string>
+ </property>
+ </action>
+ </widget>
+ <layoutdefault spacing="6" margin="11"/>
+ <customwidgets>
+ <customwidget>
+ <class>FPGAViewWidget</class>
+ <extends>QWidget</extends>
+ <header>fpgaviewwidget.h</header>
+ </customwidget>
+ </customwidgets>
+ <resources>
+ <include location="nextpnr.qrc"/>
+ </resources>
+ <connections>
+ <connection>
+ <sender>actionExit</sender>
+ <signal>triggered()</signal>
+ <receiver>MainWindow</receiver>
+ <slot>close()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>484</x>
+ <y>314</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
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 @@
+<RCC>
+ <qresource prefix="/icons">
+ <file>resources/new.png</file>
+ <file>resources/open.png</file>
+ <file>resources/save.png</file>
+ <file>resources/exit.png</file>
+ </qresource>
+</RCC>
diff --git a/gui/resources/exit.png b/gui/resources/exit.png
new file mode 100644
index 00000000..b1329cbc
--- /dev/null
+++ b/gui/resources/exit.png
Binary files differ
diff --git a/gui/resources/new.png b/gui/resources/new.png
new file mode 100644
index 00000000..f06c66ad
--- /dev/null
+++ b/gui/resources/new.png
Binary files differ
diff --git a/gui/resources/open.png b/gui/resources/open.png
new file mode 100644
index 00000000..e77fc6e7
--- /dev/null
+++ b/gui/resources/open.png
Binary files differ
diff --git a/gui/resources/save.png b/gui/resources/save.png
new file mode 100644
index 00000000..17321109
--- /dev/null
+++ b/gui/resources/save.png
Binary files differ
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 <QApplication>
+#include <iostream>
+#include "version.h"
+#include <boost/program_options.hpp>
+#include "pybindings.h"
-int main()
+int main(int argc, char *argv[])
{
- ChipArgs chipArgs;
- chipArgs.type = ChipArgs::LP384;
+ namespace po = boost::program_options;
+
+ std::string 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<std::string>(), "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();
+ }
- Design design(chipArgs);
+ if (vm.count("debug"))
+ {
+ ChipArgs chipArgs;
+ chipArgs.type = ChipArgs::LP384;
- for (auto bel : design.chip.getBels())
- printf("%s\n", design.chip.getBelName(bel).c_str());
+ 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<std::string>();
+ execute_python_file(argv[0],filename.c_str());
+ }
+
return 0;
}