An object counting plugin

Signed-off-by: Maciej Kurc <mkurc@antmicro.com>
diff --git a/Makefile b/Makefile
index 0427c0d..6e74158 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-PLUGIN_LIST := fasm xdc params selection sdc
+PLUGIN_LIST := fasm xdc params selection sdc get_count
 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/get_count-plugin/Makefile b/get_count-plugin/Makefile
new file mode 100644
index 0000000..cc0119d
--- /dev/null
+++ b/get_count-plugin/Makefile
@@ -0,0 +1,24 @@
+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 = get_count
+OBJS = $(NAME).o
+
+$(NAME).so: $(OBJS)
+	$(CXX) $(CXXFLAGS) $(LDFLAGS) -shared -o $@ $^ $(LDLIBS)
+
+.PHONY: install test
+
+install: test
+	mkdir -p $(PLUGINS_DIR)
+	cp $(NAME).so $(PLUGINS_DIR)/$(NAME).so
+
+test: $(NAME).so
+	$(MAKE) -C tests all
+
+clean:
+	rm -f *.d *.o *.so
+	$(MAKE) -C tests clean
diff --git a/get_count-plugin/get_count.cc b/get_count-plugin/get_count.cc
new file mode 100644
index 0000000..8a4a0ee
--- /dev/null
+++ b/get_count-plugin/get_count.cc
@@ -0,0 +1,133 @@
+/*
+ *  yosys -- Yosys Open SYnthesis Suite
+ *
+ *  Copyright (C) 2012  Clifford Wolf <clifford@clifford.at>
+ *  Copyright (C) 2020  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 "kernel/register.h"
+#include "kernel/rtlil.h"
+
+USING_YOSYS_NAMESPACE
+PRIVATE_NAMESPACE_BEGIN
+
+void register_in_tcl_interpreter(const std::string& command) {
+    Tcl_Interp* interp = yosys_get_tcl_interp();
+    std::string tcl_script = stringf("proc %s args { return [yosys %s {*}$args] }", command.c_str(), command.c_str());
+    Tcl_Eval(interp, tcl_script.c_str());
+}
+
+struct GetCount : public Pass {
+
+    enum class ObjectType {
+        NONE,
+        MODULE,
+        CELL,
+        WIRE
+    };
+
+    GetCount () :
+        Pass("get_count", "Returns count of various selected object types to the TCL interpreter") {
+            register_in_tcl_interpreter(pass_name);
+        }    
+
+    void help() YS_OVERRIDE {
+        log("\n");
+        log("    get_count <options> [selection]");
+        log("\n");
+        log("When used from inside the TCL interpreter returns count of selected objects.\n");
+        log("The object type to count may be given as an argument. Only one at a time.\n");
+        log("If none is given then the total count of all selected objects is returned.\n");
+        log("\n");
+        log("    -modules\n");
+        log("        Returns the count of modules in selection\n");
+        log("\n");
+        log("    -cells\n");
+        log("        Returns the count of cells in selection\n");
+        log("\n");
+        log("    -wires\n");
+        log("        Returns the count of wires in selection\n");
+        log("\n");
+    }
+    
+    void execute(std::vector<std::string> a_Args, RTLIL::Design* a_Design) YS_OVERRIDE {
+
+        // Parse args
+        ObjectType type = ObjectType::NONE;
+        if (a_Args.size() < 2) {
+            log_error("Invalid argument!\n");
+        }
+
+        if (a_Args[1] == "-modules") {
+            type = ObjectType::MODULE;
+        }
+        else if (a_Args[1] == "-cells") {
+            type = ObjectType::CELL;
+        }
+        else if (a_Args[1] == "-wires") {
+            type = ObjectType::WIRE;
+        }
+        else if (a_Args[1][0] == '-') {
+            log_error("Invalid argument '%s'!\n", a_Args[1].c_str());
+        }
+        else {
+            log_error("Object type not specified!\n");
+        }
+
+        extra_args(a_Args, 2, a_Design);
+
+        // Get the TCL interpreter
+        Tcl_Interp* tclInterp = yosys_get_tcl_interp();
+        Tcl_Obj*    tclList = Tcl_NewListObj(0, NULL);
+
+        // Count objects
+        size_t moduleCount = 0;
+        size_t cellCount   = 0;
+        size_t wireCount   = 0;
+
+        moduleCount += a_Design->selected_modules().size();
+        for (auto module : a_Design->selected_modules()) {
+            cellCount += module->selected_cells().size();
+            wireCount += module->selected_wires().size();
+        }
+
+        size_t count = 0;
+        switch (type)
+        {
+        case ObjectType::MODULE:
+            count = moduleCount;
+            break;
+        case ObjectType::CELL:
+            count = cellCount;
+            break;
+        case ObjectType::WIRE:
+            count = wireCount;
+            break;
+        default:
+            log_assert(false);
+        }
+
+        // Return the value as string to the TCL interpreter
+        std::string value = std::to_string(count);
+
+        Tcl_Obj* tclStr = Tcl_NewStringObj(value.c_str(), value.size());
+        Tcl_ListObjAppendElement(tclInterp, tclList, tclStr);
+        Tcl_SetObjResult(tclInterp, tclList);
+    }
+
+} GetCount;
+
+PRIVATE_NAMESPACE_END
diff --git a/get_count-plugin/tests/Makefile b/get_count-plugin/tests/Makefile
new file mode 100644
index 0000000..487e07e
--- /dev/null
+++ b/get_count-plugin/tests/Makefile
@@ -0,0 +1,15 @@
+TESTS = $(subst /, ,$(wildcard */))
+
+all: $(addsuffix /ok,$(TESTS))
+
+clean:
+	@find . -name "ok" | xargs rm -rf
+
+define maketest =
+$1/ok:
+	cd $1 && $(MAKE) test
+endef
+
+$(foreach _,${TESTS},$(eval $(call maketest,$_)))
+
+.PHONY: all clean
diff --git a/get_count-plugin/tests/simple/Makefile b/get_count-plugin/tests/simple/Makefile
new file mode 100644
index 0000000..1632151
--- /dev/null
+++ b/get_count-plugin/tests/simple/Makefile
@@ -0,0 +1,8 @@
+test:
+	yosys -p "tcl script.tcl"
+	touch ok
+
+clean:
+	rm -rf ok
+
+.PHONY: test clean
diff --git a/get_count-plugin/tests/simple/design.v b/get_count-plugin/tests/simple/design.v
new file mode 100644
index 0000000..935a0c1
--- /dev/null
+++ b/get_count-plugin/tests/simple/design.v
@@ -0,0 +1,23 @@
+module my_gate (
+    input  wire A,
+    output wire Y
+);
+
+    assign Y = ~A;
+endmodule
+
+module top (
+    input  wire [7:0] di,
+    output wire [7:0] do
+);
+
+    my_gate c0 (.A(di[0]), .Y(do[0]));
+    \$_BUF_ c1 (.A(di[1]), .Y(do[1]));
+    \$_BUF_ c2 (.A(di[2]), .Y(do[2]));
+    \$_BUF_ c3 (.A(di[3]), .Y(do[3]));
+    \$_BUF_ c4 (.A(di[4]), .Y(do[4]));
+    \$_NOT_ c5 (.A(di[5]), .Y(do[5]));
+    \$_NOT_ c6 (.A(di[6]), .Y(do[6]));
+    \$_NOT_ c7 (.A(di[7]), .Y(do[7]));
+
+endmodule
diff --git a/get_count-plugin/tests/simple/script.tcl b/get_count-plugin/tests/simple/script.tcl
new file mode 100644
index 0000000..6e8cdf0
--- /dev/null
+++ b/get_count-plugin/tests/simple/script.tcl
@@ -0,0 +1,30 @@
+yosys plugin -i ../../get_count.so
+yosys -import
+
+read_verilog -icells design.v
+hierarchy -auto-top
+
+set n [get_count -modules my_gate]
+puts "Module count: $n"
+if {$n != "1"} {
+    error "Invalid count"
+}
+
+set n [get_count -cells t:\$_BUF_]
+puts "BUF count: $n"
+if {$n != "4"} {
+    error "Invalid count"
+}
+
+set n [get_count -cells t:\$_NOT_]
+puts "NOT count: $n"
+if {$n != "3"} {
+    error "Invalid count"
+}
+
+set n [get_count -wires w:*]
+puts "Wire count: $n"
+if {$n != "5"} {
+    error "Invalid count"
+}
+