From d79a2808cf2446fa21d91a6141f6fbe2318c03ec Mon Sep 17 00:00:00 2001
From: Benedikt Tutzer <e1225461@student.tuwien.ac.at>
Date: Thu, 16 Aug 2018 16:00:11 +0200
Subject: [PATCH] Python Passes can now be added with the -m option or with the
 plugin command. There are still issues when run in shell mode, but they can
 be used just fine in a python script

---
 Makefile                  |  2 +-
 kernel/python_wrappers.cc | 66 ++++++++++++++++++++++++++++++++++++++-
 kernel/yosys.cc           | 26 +++++++++++++++
 kernel/yosys.h            |  5 +++
 passes/cmds/plugin.cc     | 63 +++++++++++++++++++++++++++++++++++++
 5 files changed, 160 insertions(+), 2 deletions(-)

diff --git a/Makefile b/Makefile
index 691f43798..6466bddf2 100644
--- a/Makefile
+++ b/Makefile
@@ -23,7 +23,7 @@ PYTHON_VERSION := 3.5
 
 # other configuration flags
 ENABLE_GPROF := 0
-ENABLE_DEBUG := 0
+ENABLE_DEBUG := 1
 ENABLE_NDEBUG := 0
 LINK_CURSES := 0
 LINK_TERMCAP := 0
diff --git a/kernel/python_wrappers.cc b/kernel/python_wrappers.cc
index 18b0010ae..197be0853 100644
--- a/kernel/python_wrappers.cc
+++ b/kernel/python_wrappers.cc
@@ -34,6 +34,11 @@ namespace YOSYS_PYTHON {
 		Yosys::run_pass(command);
 	}
 
+	void log(std::string text)
+	{
+		Yosys::log(text.c_str());
+	}
+
 	struct IdString
 	{
 		Yosys::RTLIL::IdString* ref_obj;
@@ -1388,7 +1393,7 @@ namespace YOSYS_PYTHON {
         virtual void py_notify_connect_tuple(Module *module, boost::python::tuple sigsig){};
         virtual void py_notify_connect_list(Module* module, boost::python::list sigsig_list){};
         virtual void py_notify_blackout(Module*){};
-        };
+    };
 
     struct MonitorWrap : Monitor, boost::python::wrapper<Monitor>
     {
@@ -1471,6 +1476,59 @@ namespace YOSYS_PYTHON {
         }
     };
 
+    struct PyPass : public Yosys::Pass
+    {
+		PyPass(std::string name, std::string short_help) : Yosys::Pass(name, short_help) { }
+	
+		virtual void execute(vector<string> args, Yosys::RTLIL::Design* d)  YS_OVERRIDE
+		{
+			boost::python::list py_args;
+            for(auto arg : args)
+                py_args.append(arg);
+			py_execute(py_args, new Design(d));
+		}
+
+		virtual void help() YS_OVERRIDE
+		{
+			py_help();
+		}
+
+		virtual void py_execute(boost::python::list args, Design* d){}
+		virtual void py_help(){}
+    };
+
+    struct PassWrap : PyPass, boost::python::wrapper<PyPass>
+    {
+
+		PassWrap(std::string name, std::string short_help) : PyPass(name, short_help) { }
+	
+		void py_execute(boost::python::list args, Design* d)
+		{
+            if(boost::python::override py_execute = this->get_override("py_execute"))
+                py_execute(args, d);
+            else
+                PyPass::py_execute(args, d);
+		}
+
+		void default_py_execute(boost::python::list args, Design* d)
+		{
+			this->PyPass::py_execute(args, d);
+		}
+
+		void py_help()
+		{
+            if(boost::python::override py_help = this->get_override("py_help"))
+                py_help();
+            else
+                PyPass::py_help();
+		}
+
+		void default_py_help()
+		{
+			this->PyPass::py_help();
+		}
+    };
+
 	void Module::register_monitor(Monitor* const m)
 	{
 		Yosys::RTLIL::Module* cpp_module = this->get_cpp_obj();
@@ -2778,6 +2836,11 @@ namespace YOSYS_PYTHON {
 		    .def("py_notify_blackout", &Monitor::py_notify_blackout, &MonitorWrap::default_py_notify_blackout)
 		    ;
 
+		class_<PassWrap, boost::noncopyable>("Pass", init<std::string, std::string>())
+		    .def("py_execute", &PyPass::py_execute, &PassWrap::default_py_execute)
+		    .def("py_help", &PyPass::py_help, &PassWrap::default_py_help)
+		    ;
+
 		class_<Initializer>("Initializer");
 		scope().attr("_hidden") = new Initializer();
 
@@ -3099,6 +3162,7 @@ namespace YOSYS_PYTHON {
 		def("const_neg", const_neg);
 
 		def("run",run);
+		def("log",log);
 
 	}
 
diff --git a/kernel/yosys.cc b/kernel/yosys.cc
index 750a154e6..8e16ba01d 100644
--- a/kernel/yosys.cc
+++ b/kernel/yosys.cc
@@ -469,21 +469,40 @@ int GetSize(RTLIL::Wire *wire)
 	return wire->width;
 }
 
+bool already_setup = false;
+
 void yosys_setup()
 {
+	if(already_setup)
+		return;
+	already_setup = true;
 	// if there are already IdString objects then we have a global initialization order bug
 	IdString empty_id;
 	log_assert(empty_id.index_ == 0);
 	IdString::get_reference(empty_id.index_);
 
+	#ifdef WITH_PYTHON
+		Py_Initialize();
+		PyRun_SimpleString("import sys");
+		PyRun_SimpleString("sys.path.append(\"./\")");
+		//PyRun_SimpleString("import libyosys");
+		//PyRun_SimpleString("sys.path.append(\"./plugins\")");
+		//PyRun_SimpleString(("sys.path.append(\""+proc_share_dirname()+"plugins\")").c_str());
+	#endif
+
 	Pass::init_register();
 	yosys_design = new RTLIL::Design;
 	yosys_celltypes.setup();
 	log_push();
 }
 
+bool already_shutdown = false;
+
 void yosys_shutdown()
 {
+	if(already_shutdown)
+		return;
+	already_shutdown = true;
 	log_pop();
 
 	delete yosys_design;
@@ -511,9 +530,16 @@ void yosys_shutdown()
 		dlclose(it.second);
 
 	loaded_plugins.clear();
+#ifdef WITH_PYTHON
+	loaded_python_plugins.clear();
+#endif
 	loaded_plugin_aliases.clear();
 #endif
 
+#ifdef WITH_PYTHON
+	Py_Finalize();
+#endif
+
 	IdString empty_id;
 	IdString::put_reference(empty_id.index_);
 }
diff --git a/kernel/yosys.h b/kernel/yosys.h
index 14cbcd610..4380a5b69 100644
--- a/kernel/yosys.h
+++ b/kernel/yosys.h
@@ -66,6 +66,8 @@
 #include <stdio.h>
 #include <limits.h>
 
+#include <Python.h>
+
 #ifndef _YOSYS_
 #  error It looks like you are trying to build Yosys without the config defines set. \
          When building Yosys with a custom make system, make sure you set all the \
@@ -317,6 +319,9 @@ extern std::vector<RTLIL::Design*> pushed_designs;
 
 // from passes/cmds/pluginc.cc
 extern std::map<std::string, void*> loaded_plugins;
+#ifdef WITH_PYTHON
+extern std::map<std::string, void*> loaded_python_plugins;
+#endif
 extern std::map<std::string, std::string> loaded_plugin_aliases;
 void load_plugin(std::string filename, std::vector<std::string> aliases);
 
diff --git a/passes/cmds/plugin.cc b/passes/cmds/plugin.cc
index 828c671de..b5d22a84a 100644
--- a/passes/cmds/plugin.cc
+++ b/passes/cmds/plugin.cc
@@ -23,9 +23,17 @@
 #  include <dlfcn.h>
 #endif
 
+#ifdef WITH_PYTHON
+#  include <boost/algorithm/string/predicate.hpp>
+#  include <Python.h>
+#endif
+
 YOSYS_NAMESPACE_BEGIN
 
 std::map<std::string, void*> loaded_plugins;
+#ifdef WITH_PYTHON
+std::map<std::string, void*> loaded_python_plugins;
+#endif
 std::map<std::string, std::string> loaded_plugin_aliases;
 
 #ifdef YOSYS_ENABLE_PLUGINS
@@ -37,6 +45,48 @@ void load_plugin(std::string filename, std::vector<std::string> aliases)
 		filename = "./" + filename;
 
 	if (!loaded_plugins.count(filename)) {
+
+		#ifdef WITH_PYTHON
+		if(boost::algorithm::ends_with(filename, ".py"))
+		{
+			int last_slash = filename.find('/');
+			filename = filename.substr(last_slash+1, filename.size());
+			filename = filename.substr(0,filename.size()-3);
+			PyObject *filename_p = PyUnicode_FromString(filename.c_str());//filename.c_str());
+			if(filename_p == NULL)
+			{
+				log_cmd_error("Issues converting `%s' to Python\n", filename.c_str());
+				return;
+			}
+			PyObject *module_p = PyImport_Import(filename_p);
+			if(module_p == NULL)
+			{
+				log_cmd_error("Can't load python module `%s'\n", filename.c_str());
+				return;
+			}/*
+			PyObject *dict_p = PyModule_GetDict(module_p);
+			if(dict_p == NULL)
+			{
+				log_cmd_error("Can't load dictionary from module `%s'\n", filename.c_str());
+				return;
+			}
+			PyObject *func_p = PyDict_GetItemString(dict_p, "test");
+			if(module_p == NULL)
+			{
+				log_cmd_error("Module `%s' does not contain test function\n", filename.c_str());
+				return;
+			}
+			PyObject *args_p = PyTuple_New(0);
+			PyObject *result_p = PyObject_CallObject(func_p, args_p);
+			if(result_p == NULL)
+					printf("Calling test failed\n");
+			printf("Loaded Python module\n");
+			*/
+			loaded_python_plugins[orig_filename] = module_p;
+			Pass::init_register();
+		} else {
+		#endif
+
 		void *hdl = dlopen(filename.c_str(), RTLD_LAZY|RTLD_LOCAL);
 		if (hdl == NULL && orig_filename.find('/') == std::string::npos)
 			hdl = dlopen((proc_share_dirname() + "plugins/" + orig_filename + ".so").c_str(), RTLD_LAZY|RTLD_LOCAL);
@@ -44,6 +94,10 @@ void load_plugin(std::string filename, std::vector<std::string> aliases)
 			log_cmd_error("Can't load module `%s': %s\n", filename.c_str(), dlerror());
 		loaded_plugins[orig_filename] = hdl;
 		Pass::init_register();
+
+		#ifdef WITH_PYTHON
+		}
+		#endif
 	}
 
 	for (auto &alias : aliases)
@@ -107,7 +161,11 @@ struct PluginPass : public Pass {
 		if (list_mode)
 		{
 			log("\n");
+#ifdef WITH_PYTHON
+			if (loaded_plugins.empty() and loaded_python_plugins.empty())
+#else
 			if (loaded_plugins.empty())
+#endif
 				log("No plugins loaded.\n");
 			else
 				log("Loaded plugins:\n");
@@ -115,6 +173,11 @@ struct PluginPass : public Pass {
 			for (auto &it : loaded_plugins)
 				log("  %s\n", it.first.c_str());
 
+#ifdef WITH_PYTHON
+			for (auto &it : loaded_python_plugins)
+				log("  %s\n", it.first.c_str());
+#endif
+
 			if (!loaded_plugin_aliases.empty()) {
 				log("\n");
 				int max_alias_len = 1;