Introspection: Add initial version of plugin with get_nets command

Signed-off-by: Tomasz Michalak <tmichalak@antmicro.com>
diff --git a/Makefile b/Makefile
index 72611b4..c397859 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-PLUGIN_LIST := fasm xdc params selection sdc get_count ql-iob
+PLUGIN_LIST := fasm xdc params selection sdc get_count ql-iob design_introspection
 PLUGINS := $(foreach plugin,$(PLUGIN_LIST),$(plugin).so)
 PLUGINS_INSTALL := $(foreach plugin,$(PLUGIN_LIST),install_$(plugin))
 PLUGINS_CLEAN := $(foreach plugin,$(PLUGIN_LIST),clean_$(plugin))
diff --git a/design_introspection-plugin/Makefile b/design_introspection-plugin/Makefile
new file mode 100644
index 0000000..1b65892
--- /dev/null
+++ b/design_introspection-plugin/Makefile
@@ -0,0 +1,26 @@
+CXX = $(shell yosys-config --cxx)
+CXXFLAGS = $(shell yosys-config --cxxflags)
+LDFLAGS = $(shell yosys-config --ldflags)
+LDLIBS = $(shell yosys-config --ldlibs)
+PLUGINS_DIR = $(shell yosys-config --datdir)/plugins
+
+NAME = design_introspection
+OBJS = $(NAME).o get_nets.o
+
+$(NAME).so: $(OBJS)
+	$(CXX) $(CXXFLAGS) $(LDFLAGS) -shared -o $@ $^ $(LDLIBS)
+
+install_plugin: $(NAME).so
+	mkdir -p $(PLUGINS_DIR)
+	cp $< $(PLUGINS_DIR)/$<
+
+test:
+	$(MAKE) -C tests all
+
+.PHONY: install
+install: install_plugin
+
+clean:
+	rm -f *.d *.o *.so
+	$(MAKE) -C tests clean
+
diff --git a/design_introspection-plugin/design_introspection.cc b/design_introspection-plugin/design_introspection.cc
new file mode 100644
index 0000000..51921b0
--- /dev/null
+++ b/design_introspection-plugin/design_introspection.cc
@@ -0,0 +1,33 @@
+/*
+ *  yosys -- Yosys Open SYnthesis Suite
+ *
+ *  Copyright (C) 2012  Clifford Wolf <clifford@clifford.at>
+ *  Copyright (C) 2019  The Symbiflow Authors
+ *
+ *  Permission to use, copy, modify, and/or distribute this software for any
+ *  purpose with or without fee is hereby granted, provided that the above
+ *  copyright notice and this permission notice appear in all copies.
+ *
+ *  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ *  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ *  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ *  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ *  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ *  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ *  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ */
+
+#include "get_nets.h"
+
+USING_YOSYS_NAMESPACE
+
+PRIVATE_NAMESPACE_BEGIN
+
+struct DesignIntrospection {
+    DesignIntrospection(){}
+    GetNets get_nets_cmd;
+} DesignIntrospection;
+
+
+PRIVATE_NAMESPACE_END
diff --git a/design_introspection-plugin/get_nets.cc b/design_introspection-plugin/get_nets.cc
new file mode 100644
index 0000000..4b5e4a0
--- /dev/null
+++ b/design_introspection-plugin/get_nets.cc
@@ -0,0 +1,138 @@
+#include "get_nets.h"
+#include <regex>
+#include "kernel/log.h"
+#include "kernel/register.h"
+#include "kernel/rtlil.h"
+
+USING_YOSYS_NAMESPACE
+
+void GetNets::help() {
+    //   |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
+    log("\n");
+    log("   get_nets [-quiet] [-filter filter_expression] <net_selection> \n");
+    log("\n");
+    log("Get matching nets\n");
+    log("\n");
+    log("Print the output to stdout too. This is useful when all Yosys is "
+        "executed.\n");
+    log("\n");
+    log("    -filter\n");
+    log("        Name and value of attribute to be taken into account.\n");
+    log("        e.g. -filter { attr == \"true\" }\n");
+    log("\n");
+    log("    -quiet\n");
+    log("        Don't print the result of the execution to stdout.\n");
+    log("\n");
+    log("    <net_selection>\n");
+    log("        Selection of net name. Default are all nets in the design.\n");
+    log("\n");
+}
+
+void GetNets::execute(std::vector<std::string> args, RTLIL::Design* design) {
+    RTLIL::Module* top_module = design->top_module();
+    if (top_module == nullptr) {
+	log_cmd_error("No top module detected\n");
+    }
+
+    size_t argidx;
+    std::vector<std::pair<std::string, std::string>> filters;
+    bool is_quiet = false;
+    bool has_filter = false;
+
+    // Parse command arguments
+    for (argidx = 1; argidx < args.size(); argidx++) {
+	std::string arg = args[argidx];
+	if (arg == "-quiet") {
+	    is_quiet = true;
+	    continue;
+	}
+
+	if (arg == "-filter" and argidx + 1 < args.size()) {
+	    std::string filter_arg = args[++argidx];
+
+	    // Remove spaces
+	    filter_arg.erase(
+	        std::remove_if(filter_arg.begin(), filter_arg.end(), isspace),
+	        filter_arg.end());
+
+	    // Parse filters
+	    std::regex filter_attr_regex("(\\w+\\s?==\\s?\\w+)([(||)(&&)]*)");
+	    std::regex_token_iterator<std::string::iterator> regex_end;
+	    std::regex_token_iterator<std::string::iterator> matches(
+	        filter_arg.begin(), filter_arg.end(), filter_attr_regex, 1);
+	    if (matches == regex_end) {
+		log_warning(
+		    "Currently -filter switch supports only a single "
+		    "'equal(==)' condition expression, the rest will be "
+		    "ignored\n");
+	    }
+
+	    while (matches != regex_end) {
+		std::string filter(*matches++);
+		auto separator = filter.find("==");
+		if (separator == std::string::npos) {
+		    log_cmd_error("Incorrect filter expression: %s\n",
+		                  args[argidx].c_str());
+		}
+		filters.emplace_back(filter.substr(0, separator),
+		                     filter.substr(separator + 2));
+	    }
+	    size_t filter_cnt = filters.size();
+	    has_filter = filter_cnt > 0;
+	    if (filter_cnt > 1) {
+		log_warning(
+		    "Currently -filter switch supports only a single "
+		    "'equal(==)' condition expression, the rest will be "
+		    "ignored\n");
+	    }
+	    continue;
+	}
+
+	if (arg.size() > 0 and arg[0] == '-') {
+	    log_cmd_error("Unknown option %s.\n", arg.c_str());
+	}
+
+	break;
+    }
+
+    // Add name of top module to selection string
+    std::vector<std::string> selection_args;
+    std::transform(args.begin() + argidx, args.end(),
+                   std::back_inserter(selection_args), [&](std::string& net) {
+	               return RTLIL::unescape_id(top_module->name) +
+	                      "/w:" + net;
+                   });
+
+    // Execute the selection
+    extra_args(selection_args, 0, design);
+    if (design->selected_modules().empty()) {
+	if (!is_quiet) {
+	    log_warning("Specified net not found in design\n");
+	}
+    }
+
+    // Pack the selected nets into Tcl List
+    Tcl_Interp* interp = yosys_get_tcl_interp();
+    Tcl_Obj* tcl_list = Tcl_NewListObj(0, NULL);
+    for (auto module : design->selected_modules()) {
+	for (auto wire : module->selected_wires()) {
+	    if (has_filter) {
+		std::pair<std::string, std::string> filter = filters.at(0);
+		std::string attr_value = wire->get_string_attribute(
+		    RTLIL::IdString(RTLIL::escape_id(filter.first)));
+		if (attr_value.compare(filter.second)) {
+		    continue;
+		}
+	    }
+	    if (!is_quiet) {
+		log("%s ", id2cstr(wire->name));
+	    }
+	    Tcl_Obj* value_obj = Tcl_NewStringObj(id2cstr(wire->name), -1);
+	    Tcl_ListObjAppendElement(interp, tcl_list, value_obj);
+	}
+    }
+    if (!is_quiet) {
+	log("\n");
+    }
+    Tcl_SetObjResult(interp, tcl_list);
+}
diff --git a/design_introspection-plugin/get_nets.h b/design_introspection-plugin/get_nets.h
new file mode 100644
index 0000000..8b66abe
--- /dev/null
+++ b/design_introspection-plugin/get_nets.h
@@ -0,0 +1,10 @@
+#include "kernel/register.h"
+
+USING_YOSYS_NAMESPACE
+
+struct GetNets : public Pass {
+	GetNets() : Pass("get_nets", "Print matching nets") {}
+
+	void help() override;
+	void execute(std::vector<std::string> args, RTLIL::Design* design) override;
+};
diff --git a/design_introspection-plugin/tests/Makefile b/design_introspection-plugin/tests/Makefile
new file mode 100644
index 0000000..e31bab6
--- /dev/null
+++ b/design_introspection-plugin/tests/Makefile
@@ -0,0 +1,32 @@
+TESTS = get_nets
+.PHONY: $(TESTS)
+
+get_nets_verify = $(call compare,get_nets,txt)
+
+all: $(TESTS)
+compare = diff $(1)/$(1).golden.$(2) $(1)/$(1).$(2)
+
+define test_tpl =
+$(1): $(1)/$(1).sdc
+	$$($(1)_verify)
+	RETVAL=$$$$? ; \
+	if [ $$$$RETVAL -eq 0 ]; then \
+		echo "$(1) PASS"; \
+		true; \
+	else \
+		echo "$(1) FAIL"; \
+		false; \
+	fi
+
+$(1)/$(1).sdc: $(1)/$(1).v
+	cd $(1); \
+	INPUT_SDC_FILE=$(1).input.sdc \
+	OUTPUT_SDC_FILE=$(1).sdc \
+	yosys -p "tcl $(1).tcl" -l yosys.log
+
+endef
+
+$(foreach test,$(TESTS),$(eval $(call test_tpl,$(test))))
+
+clean:
+	rm -rf $(foreach test,$(TESTS),$(test)/$(test).sdc $(test)/$(test).txt $(test)/yosys.log)