From f728927307b959f1e97b11bf9ecedee53220d7a1 Mon Sep 17 00:00:00 2001
From: Jannis Harder <me@jix.one>
Date: Tue, 19 Dec 2023 14:37:27 +0100
Subject: [PATCH 01/14] Add builtin celltype $scopeinfo

Only declares the cell interface, doesn't make anything use or
understand $scopeinfo yet.
---
 kernel/celltypes.h       | 1 +
 kernel/rtlil.cc          | 9 +++++++++
 kernel/satgen.cc         | 5 +++++
 techlibs/common/simlib.v | 7 +++++++
 4 files changed, 22 insertions(+)

diff --git a/kernel/celltypes.h b/kernel/celltypes.h
index 77cb3e324..fde6624e1 100644
--- a/kernel/celltypes.h
+++ b/kernel/celltypes.h
@@ -108,6 +108,7 @@ struct CellTypes
 		setup_type(ID($overwrite_tag), {ID::A, ID::SET, ID::CLR}, pool<RTLIL::IdString>());
 		setup_type(ID($original_tag), {ID::A}, {ID::Y});
 		setup_type(ID($future_ff), {ID::A}, {ID::Y});
+		setup_type(ID($scopeinfo), {}, {});
 	}
 
 	void setup_internals_eval()
diff --git a/kernel/rtlil.cc b/kernel/rtlil.cc
index 125730f29..8781b6a89 100644
--- a/kernel/rtlil.cc
+++ b/kernel/rtlil.cc
@@ -1769,6 +1769,15 @@ namespace {
 				return;
 			}
 
+			if (cell->type == ID($scopeinfo)) {
+				param(ID::TYPE);
+				check_expected();
+				std::string scope_type = cell->getParam(ID::TYPE).decode_string();
+				if (scope_type != "module" && scope_type != "struct")
+					error(__LINE__);
+				return;
+			}
+
 			if (cell->type == ID($_BUF_))    { port(ID::A,1); port(ID::Y,1); check_expected(); return; }
 			if (cell->type == ID($_NOT_))    { port(ID::A,1); port(ID::Y,1); check_expected(); return; }
 			if (cell->type == ID($_AND_))    { port(ID::A,1); port(ID::B,1); port(ID::Y,1); check_expected(); return; }
diff --git a/kernel/satgen.cc b/kernel/satgen.cc
index 3a2fa4735..baaf22d1f 100644
--- a/kernel/satgen.cc
+++ b/kernel/satgen.cc
@@ -1379,6 +1379,11 @@ bool SatGen::importCell(RTLIL::Cell *cell, int timestep)
 		return true;
 	}
 
+	if (cell->type == ID($scopeinfo))
+	{
+		return true;
+	}
+
 	// Unsupported internal cell types: $pow $fsm $mem*
 	// .. and all sequential cells with asynchronous inputs
 	return false;
diff --git a/techlibs/common/simlib.v b/techlibs/common/simlib.v
index 930d2000b..489281f26 100644
--- a/techlibs/common/simlib.v
+++ b/techlibs/common/simlib.v
@@ -2763,3 +2763,10 @@ assign Y = A;
 endmodule
 
 // --------------------------------------------------------
+
+(* noblackbox *)
+module \$scopeinfo ();
+
+parameter TYPE = "";
+
+endmodule

From 8902fc94b6736e74e8da105d6cc8f32bf2f44817 Mon Sep 17 00:00:00 2001
From: Jannis Harder <me@jix.one>
Date: Tue, 19 Dec 2023 16:23:38 +0100
Subject: [PATCH 02/14] Suport $scopeinfo in flatten and opt_clean

---
 passes/opt/opt_clean.cc   |  11 +++-
 passes/techmap/flatten.cc | 114 ++++++++++++++++++++++++++++++++------
 2 files changed, 105 insertions(+), 20 deletions(-)

diff --git a/passes/opt/opt_clean.cc b/passes/opt/opt_clean.cc
index 2b24944cf..b3e43c18c 100644
--- a/passes/opt/opt_clean.cc
+++ b/passes/opt/opt_clean.cc
@@ -35,10 +35,12 @@ struct keep_cache_t
 {
 	Design *design;
 	dict<Module*, bool> cache;
+	bool purge_mode = false;
 
-	void reset(Design *design = nullptr)
+	void reset(Design *design = nullptr, bool purge_mode = false)
 	{
 		this->design = design;
+		this->purge_mode = purge_mode;
 		cache.clear();
 	}
 
@@ -88,6 +90,9 @@ struct keep_cache_t
 		if (cell->has_keep_attr())
 			return true;
 
+		if (!purge_mode && cell->type == ID($scopeinfo))
+			return true;
+
 		if (cell->module && cell->module->design)
 			return query(cell->module->design->module(cell->type));
 
@@ -236,6 +241,8 @@ int count_nontrivial_wire_attrs(RTLIL::Wire *w)
 {
 	int count = w->attributes.size();
 	count -= w->attributes.count(ID::src);
+	count -= w->attributes.count(ID::hdlname);
+	count -= w->attributes.count(ID(scopename));
 	count -= w->attributes.count(ID::unused_bits);
 	return count;
 }
@@ -661,7 +668,7 @@ struct OptCleanPass : public Pass {
 		}
 		extra_args(args, argidx, design);
 
-		keep_cache.reset(design);
+		keep_cache.reset(design, purge_mode);
 
 		ct_reg.setup_internals_mem();
 		ct_reg.setup_internals_anyinit();
diff --git a/passes/techmap/flatten.cc b/passes/techmap/flatten.cc
index 4ddc4aff1..ea5855a09 100644
--- a/passes/techmap/flatten.cc
+++ b/passes/techmap/flatten.cc
@@ -46,24 +46,6 @@ IdString map_name(RTLIL::Cell *cell, T *object)
 	return cell->module->uniquify(concat_name(cell, object->name));
 }
 
-template<class T>
-void map_attributes(RTLIL::Cell *cell, T *object, IdString orig_object_name)
-{
-	if (object->has_attribute(ID::src))
-		object->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src));
-
-	// Preserve original names via the hdlname attribute, but only for objects with a fully public name.
-	if (cell->name[0] == '\\' && (object->has_attribute(ID::hdlname) || orig_object_name[0] == '\\')) {
-		std::vector<std::string> hierarchy;
-		if (object->has_attribute(ID::hdlname))
-			hierarchy = object->get_hdlname_attribute();
-		else
-			hierarchy.push_back(orig_object_name.str().substr(1));
-		hierarchy.insert(hierarchy.begin(), cell->name.str().substr(1));
-		object->set_hdlname_attribute(hierarchy);
-	}
-}
-
 void map_sigspec(const dict<RTLIL::Wire*, RTLIL::Wire*> &map, RTLIL::SigSpec &sig, RTLIL::Module *into = nullptr)
 {
 	vector<SigChunk> chunks = sig;
@@ -76,6 +58,54 @@ void map_sigspec(const dict<RTLIL::Wire*, RTLIL::Wire*> &map, RTLIL::SigSpec &si
 struct FlattenWorker
 {
 	bool ignore_wb = false;
+	bool create_scopeinfo = true;
+	bool create_scopename = false;
+
+	template<class T>
+	void map_attributes(RTLIL::Cell *cell, T *object, IdString orig_object_name)
+	{
+		if (!create_scopeinfo && object->has_attribute(ID::src))
+			object->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src));
+
+		// Preserve original names via the hdlname attribute, but only for objects with a fully public name.
+		// If the '-scopename' option is used, also preserve the containing scope of private objects if their scope is fully public.
+		if (cell->name[0] == '\\') {
+			if (object->has_attribute(ID::hdlname) || orig_object_name[0] == '\\') {
+				std::string new_hdlname;
+
+				if (cell->has_attribute(ID::hdlname)) {
+					new_hdlname = cell->get_string_attribute(ID(hdlname));
+				} else {
+					log_assert(!cell->name.empty());
+					new_hdlname = cell->name.c_str() + 1;
+				}
+				new_hdlname += ' ';
+
+				if (object->has_attribute(ID::hdlname)) {
+					new_hdlname += object->get_string_attribute(ID(hdlname));
+				} else {
+					log_assert(!orig_object_name.empty());
+					new_hdlname += orig_object_name.c_str() + 1;
+				}
+				object->set_string_attribute(ID(hdlname), new_hdlname);
+			} else if (object->has_attribute(ID(scopename))) {
+				std::string new_scopename;
+
+				if (cell->has_attribute(ID::hdlname)) {
+					new_scopename = cell->get_string_attribute(ID(hdlname));
+				} else {
+					log_assert(!cell->name.empty());
+					new_scopename = cell->name.c_str() + 1;
+				}
+				new_scopename += ' ';
+				new_scopename += object->get_string_attribute(ID(scopename));
+				object->set_string_attribute(ID(scopename), new_scopename);
+			} else if (create_scopename) {
+				log_assert(!cell->name.empty());
+				object->set_string_attribute(ID(scopename), cell->name.c_str() + 1);
+			}
+		}
+	}
 
 	void flatten_cell(RTLIL::Design *design, RTLIL::Module *module, RTLIL::Cell *cell, RTLIL::Module *tpl, SigMap &sigmap, std::vector<RTLIL::Cell*> &new_cells)
 	{
@@ -220,7 +250,33 @@ struct FlattenWorker
 			sigmap.add(new_conn.first, new_conn.second);
 		}
 
+		RTLIL::Cell *scopeinfo = nullptr;
+		RTLIL::IdString cell_name = cell->name;
+
+		if (create_scopeinfo && cell_name.isPublic())
+		{
+			// The $scopeinfo's name will be changed below after removing the flattened cell
+			scopeinfo = module->addCell(NEW_ID, ID($scopeinfo));
+			scopeinfo->setParam(ID::TYPE, RTLIL::Const("module"));
+
+			for (auto const &attr : cell->attributes)
+			{
+				if (attr.first == ID::hdlname)
+					scopeinfo->attributes.insert(attr);
+				else
+					scopeinfo->attributes.emplace(stringf("\\cell_%s", RTLIL::unescape_id(attr.first).c_str()), attr.second);
+			}
+
+			for (auto const &attr : tpl->attributes)
+				scopeinfo->attributes.emplace(stringf("\\module_%s", RTLIL::unescape_id(attr.first).c_str()), attr.second);
+
+			scopeinfo->attributes.emplace(ID(module), RTLIL::unescape_id(tpl->name));
+		}
+
 		module->remove(cell);
+
+		if (scopeinfo != nullptr)
+			module->rename(scopeinfo, cell_name);
 	}
 
 	void flatten_module(RTLIL::Design *design, RTLIL::Module *module, pool<RTLIL::Module*> &used_modules)
@@ -275,6 +331,20 @@ struct FlattenPass : public Pass {
 		log("    -wb\n");
 		log("        Ignore the 'whitebox' attribute on cell implementations.\n");
 		log("\n");
+		log("    -noscopeinfo\n");
+		log("        Do not create '$scopeinfo' cells that preserve attributes of cells and\n");
+		log("        modules that were removed during flattening. With this option, the\n");
+		log("        'src' attribute of a given cell is merged into all objects replacing\n");
+		log("        that cell, with multiple distinct 'src' locations separated by '|'.\n");
+		log("        Without this option these 'src' locations can be found via the\n");
+		log("        cell_src' and 'module_src' attribute of '$scopeinfo' cells.\n");
+		log("\n");
+		log("    -scopename\n");
+		log("        Create 'scopename' attributes for objects with a private name. This\n");
+		log("        attribute records the 'hdlname' of the enclosing scope. For objects\n");
+		log("        with a public name the enclosing scope can be found via their\n");
+		log("        'hdlname' attribute.\n");
+		log("\n");
 	}
 	void execute(std::vector<std::string> args, RTLIL::Design *design) override
 	{
@@ -289,6 +359,14 @@ struct FlattenPass : public Pass {
 				worker.ignore_wb = true;
 				continue;
 			}
+			if (args[argidx] == "-noscopeinfo") {
+				worker.create_scopeinfo = false;
+				continue;
+			}
+			if (args[argidx] == "-scopename") {
+				worker.create_scopename = true;
+				continue;
+			}
 			break;
 		}
 		extra_args(args, argidx, design);

From 9288107f4345fba9f0364036415b5b9b88178b5b Mon Sep 17 00:00:00 2001
From: Jannis Harder <me@jix.one>
Date: Tue, 19 Dec 2023 19:47:09 +0100
Subject: [PATCH 03/14] Test flatten and opt_clean's $scopeinfo handling

---
 tests/various/scopeinfo.ys | 110 +++++++++++++++++++++++++++++++++++++
 1 file changed, 110 insertions(+)
 create mode 100644 tests/various/scopeinfo.ys

diff --git a/tests/various/scopeinfo.ys b/tests/various/scopeinfo.ys
new file mode 100644
index 000000000..f8d4ca31b
--- /dev/null
+++ b/tests/various/scopeinfo.ys
@@ -0,0 +1,110 @@
+read_verilog <<EOT
+(* module_attr = "module_attr" *)
+module some_mod(input a, output y);
+    assign y = a;
+endmodule
+
+module top(input a, output y);
+    (* inst_attr = "inst_attr" *)
+    some_mod some_inst(.a(a), .y(y));
+endmodule
+EOT
+
+hierarchy -top top
+flatten
+
+select -assert-count 1 top/n:some_inst top/t:$scopeinfo %i
+select -assert-count 1 top/n:some_inst top/r:TYPE=module %i
+select -assert-count 1 top/n:some_inst top/a:cell_inst_attr=inst_attr %i
+select -assert-count 1 top/n:some_inst top/a:module_module_attr=module_attr %i
+select -assert-count 1 top/n:some_inst top/a:cell_src %i
+select -assert-count 1 top/n:some_inst top/a:module_src %i
+
+opt_clean
+select -assert-count 1 top/t:$scopeinfo
+opt_clean -purge
+select -assert-count 0 top/t:$scopeinfo
+
+design -reset
+
+read_verilog <<EOT
+(* module_attr = "module_attr_deep" *)
+module some_mod_deep(input a, output y);
+    assign y = a;
+endmodule
+
+(* module_attr = "module_attr" *)
+module some_mod(input a, output y);
+    (* inst_attr = "inst_attr_deep" *)
+    some_mod_deep some_inst_deep(.a(a), .y(y));
+endmodule
+
+module top(input a, output y);
+    (* inst_attr = "inst_attr" *)
+    some_mod some_inst(.a(a), .y(y));
+endmodule
+EOT
+
+hierarchy -top top
+flatten
+
+select -assert-count 2 top/t:$scopeinfo
+select -assert-count 1 top/n:some_inst top/t:$scopeinfo %i
+select -assert-count 1 top/a:hdlname=some_inst?some_inst_deep
+
+select -assert-count 1 top/n:some_inst top/r:TYPE=module %i
+select -assert-count 1 top/n:some_inst top/a:cell_inst_attr=inst_attr %i
+select -assert-count 1 top/n:some_inst top/a:module_module_attr=module_attr %i
+select -assert-count 1 top/n:some_inst top/a:cell_src %i
+select -assert-count 1 top/n:some_inst top/a:module_src %i
+
+select -assert-count 1 top/a:hdlname=some_inst?some_inst_deep top/r:TYPE=module %i
+select -assert-count 1 top/a:hdlname=some_inst?some_inst_deep top/a:cell_inst_attr=inst_attr_deep %i
+select -assert-count 1 top/a:hdlname=some_inst?some_inst_deep top/a:module_module_attr=module_attr_deep %i
+select -assert-count 1 top/a:hdlname=some_inst?some_inst_deep top/a:cell_src %i
+select -assert-count 1 top/a:hdlname=some_inst?some_inst_deep top/a:module_src %i
+
+
+design -reset
+
+read_verilog <<EOT
+(* module_attr = "module_attr_deep" *)
+(* keep_hierarchy *)
+module some_mod_deep(input a, output y);
+    assign y = a;
+endmodule
+
+(* module_attr = "module_attr" *)
+module some_mod(input a, output y);
+    (* inst_attr = "inst_attr_deep" *)
+    some_mod_deep some_inst_deep(.a(a), .y(y));
+endmodule
+
+module top(input a, output y);
+    (* inst_attr = "inst_attr" *)
+    some_mod some_inst(.a(a), .y(y));
+endmodule
+EOT
+
+hierarchy -top top
+flatten top
+
+select -assert-count 1 top/t:$scopeinfo
+setattr -mod -unset keep_hierarchy *
+flatten
+
+select -assert-count 2 top/t:$scopeinfo
+select -assert-count 1 top/n:some_inst top/t:$scopeinfo %i
+select -assert-count 1 top/a:hdlname=some_inst?some_inst_deep
+
+select -assert-count 1 top/n:some_inst top/r:TYPE=module %i
+select -assert-count 1 top/n:some_inst top/a:cell_inst_attr=inst_attr %i
+select -assert-count 1 top/n:some_inst top/a:module_module_attr=module_attr %i
+select -assert-count 1 top/n:some_inst top/a:cell_src %i
+select -assert-count 1 top/n:some_inst top/a:module_src %i
+
+select -assert-count 1 top/a:hdlname=some_inst?some_inst_deep top/r:TYPE=module %i
+select -assert-count 1 top/a:hdlname=some_inst?some_inst_deep top/a:cell_inst_attr=inst_attr_deep %i
+select -assert-count 1 top/a:hdlname=some_inst?some_inst_deep top/a:module_module_attr=module_attr_deep %i
+select -assert-count 1 top/a:hdlname=some_inst?some_inst_deep top/a:cell_src %i
+select -assert-count 1 top/a:hdlname=some_inst?some_inst_deep top/a:module_src %i
\ No newline at end of file

From bfd9cf63dbfefd161de2fc7d5c7fbbce21d5ee9b Mon Sep 17 00:00:00 2001
From: Jannis Harder <me@jix.one>
Date: Thu, 11 Jan 2024 14:10:13 +0100
Subject: [PATCH 04/14] Ignore $scopeinfo in opt_merge

---
 passes/opt/opt_merge.cc | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/passes/opt/opt_merge.cc b/passes/opt/opt_merge.cc
index 248ba8091..eb3aa462e 100644
--- a/passes/opt/opt_merge.cc
+++ b/passes/opt/opt_merge.cc
@@ -272,6 +272,9 @@ struct OptMergeWorker
 				if ((!mode_share_all && !ct.cell_known(cell->type)) || !cell->known())
 					continue;
 
+				if (cell->type == ID($scopeinfo))
+					continue;
+
 				uint64_t hash = hash_cell_parameters_and_connections(cell);
 				auto r = sharemap.insert(std::make_pair(hash, cell));
 				if (!r.second) {

From 10d5d358d2b8f82767953e7c5be44637a245582b Mon Sep 17 00:00:00 2001
From: Jannis Harder <me@jix.one>
Date: Thu, 11 Jan 2024 14:10:25 +0100
Subject: [PATCH 05/14] Ignore $scopeinfo in write_aiger

While SBY's aiger flow already removes non-assertion driving logic,
there are some uses of write_aiger outside of SBY that could end up with
$scopeinfo cells, so we explicitly ignore them.

The write_btor backend works differently and due to the way it
recursively visits cells, it would never reach isolated cells like
$scopeinfo.
---
 backends/aiger/aiger.cc | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/backends/aiger/aiger.cc b/backends/aiger/aiger.cc
index f77a64978..fe4f7681d 100644
--- a/backends/aiger/aiger.cc
+++ b/backends/aiger/aiger.cc
@@ -320,6 +320,9 @@ struct AigerWriter
 				continue;
 			}
 
+			if (cell->type == ID($scopeinfo))
+				continue;
+
 			log_error("Unsupported cell type: %s (%s)\n", log_id(cell->type), log_id(cell));
 		}
 

From 5cfbc1604cb75b39ce92120bab61168cea84da35 Mon Sep 17 00:00:00 2001
From: Jannis Harder <me@jix.one>
Date: Thu, 11 Jan 2024 14:54:02 +0100
Subject: [PATCH 06/14] Ignore $scopeinfo in write_edif

---
 backends/edif/edif.cc | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/backends/edif/edif.cc b/backends/edif/edif.cc
index 00fd7f54e..553eb23d6 100644
--- a/backends/edif/edif.cc
+++ b/backends/edif/edif.cc
@@ -213,6 +213,9 @@ struct EdifBackend : public Backend {
 
 			for (auto cell : module->cells())
 			{
+				if (cell->type == ID($scopeinfo))
+					continue;
+
 				if (design->module(cell->type) == nullptr || design->module(cell->type)->get_blackbox_attribute()) {
 					lib_cell_ports[cell->type];
 					for (auto p : cell->connections())

From 59a60c76fe1f52d0f1000bb2274bbdbc455af827 Mon Sep 17 00:00:00 2001
From: Jannis Harder <me@jix.one>
Date: Thu, 11 Jan 2024 14:57:34 +0100
Subject: [PATCH 07/14] Ignore $scopeinfo in write_blif

---
 backends/blif/blif.cc | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/backends/blif/blif.cc b/backends/blif/blif.cc
index 8e2c088c4..788b7f951 100644
--- a/backends/blif/blif.cc
+++ b/backends/blif/blif.cc
@@ -226,6 +226,9 @@ struct BlifDumper
 
 		for (auto cell : module->cells())
 		{
+			if (cell->type == ID($scopeinfo))
+				continue;
+
 			if (config->unbuf_types.count(cell->type)) {
 				auto portnames = config->unbuf_types.at(cell->type);
 				f << stringf(".names %s %s\n1 1\n",

From 55d8425468da6153a2b2bc9a08eb1fd4fec95cb4 Mon Sep 17 00:00:00 2001
From: Jannis Harder <me@jix.one>
Date: Thu, 11 Jan 2024 15:00:09 +0100
Subject: [PATCH 08/14] Ignore $scopeinfo in write_firrtl

---
 backends/firrtl/firrtl.cc | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/backends/firrtl/firrtl.cc b/backends/firrtl/firrtl.cc
index fc1d62891..dc76dbeec 100644
--- a/backends/firrtl/firrtl.cc
+++ b/backends/firrtl/firrtl.cc
@@ -980,6 +980,9 @@ struct FirrtlWorker
 				register_reverse_wire_map(y_id, cell->getPort(ID::Y));
 				continue;
 			}
+
+			if (cell->type == ID($scopeinfo))
+				continue;
 			log_error("Cell type not supported: %s (%s.%s)\n", log_id(cell->type), log_id(module), log_id(cell));
 		}
 

From 418bf61b8d70b1683cb0644dfc4646a271031ce4 Mon Sep 17 00:00:00 2001
From: Jannis Harder <me@jix.one>
Date: Thu, 11 Jan 2024 15:11:11 +0100
Subject: [PATCH 09/14] Ignore $scopeinfo in write_smv

---
 backends/smv/smv.cc | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/backends/smv/smv.cc b/backends/smv/smv.cc
index 49c2cc7a6..44e200384 100644
--- a/backends/smv/smv.cc
+++ b/backends/smv/smv.cc
@@ -573,6 +573,9 @@ struct SmvWorker
 				continue;
 			}
 
+			if (cell->type == ID($scopeinfo))
+				continue;
+
 			if (cell->type[0] == '$') {
 				if (cell->type.in(ID($dffe), ID($sdff), ID($sdffe), ID($sdffce)) || cell->type.str().substr(0, 6) == "$_SDFF" || (cell->type.str().substr(0, 6) == "$_DFFE" && cell->type.str().size() == 10)) {
 					log_error("Unsupported cell type %s for cell %s.%s -- please run `dffunmap` before `write_smv`.\n",

From 5ee8bebde4ec9893f57917f9580700ad50756190 Mon Sep 17 00:00:00 2001
From: Jannis Harder <me@jix.one>
Date: Thu, 11 Jan 2024 15:12:32 +0100
Subject: [PATCH 10/14] Ignore $scopeinfo in write_spice

---
 backends/spice/spice.cc | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/backends/spice/spice.cc b/backends/spice/spice.cc
index f260276eb..1160a01a1 100644
--- a/backends/spice/spice.cc
+++ b/backends/spice/spice.cc
@@ -72,6 +72,9 @@ static void print_spice_module(std::ostream &f, RTLIL::Module *module, RTLIL::De
 
 	for (auto cell : module->cells())
 	{
+		if (cell->type == ID($scopeinfo))
+			continue;
+
 		f << stringf("X%d", cell_counter++);
 
 		std::vector<RTLIL::SigSpec> port_sigs;

From f31fb95963507d6ddd00cd25a2eaa90d6a191ae7 Mon Sep 17 00:00:00 2001
From: Jannis Harder <me@jix.one>
Date: Thu, 11 Jan 2024 15:18:37 +0100
Subject: [PATCH 11/14] Ignore $scopeinfo in write_verilog

---
 backends/verilog/verilog_backend.cc | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/backends/verilog/verilog_backend.cc b/backends/verilog/verilog_backend.cc
index 988eef658..05b7c6c40 100644
--- a/backends/verilog/verilog_backend.cc
+++ b/backends/verilog/verilog_backend.cc
@@ -1871,6 +1871,13 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell)
 
 void dump_cell(std::ostream &f, std::string indent, RTLIL::Cell *cell)
 {
+	// To keep the output compatible with other tools we ignore $scopeinfo
+	// cells that exist only to hold metadata. If in the future that metadata
+	// should be exposed as part of the write_verilog output it should be
+	// opt-in and/or represented as something else than a $scopeinfo cell.
+	if (cell->type == ID($scopeinfo))
+		return;
+
 	// Handled by dump_memory
 	if (cell->is_mem_cell())
 		return;

From bbe39762aded86bf621e6f344a5b7e5b804c73d6 Mon Sep 17 00:00:00 2001
From: Jannis Harder <me@jix.one>
Date: Fri, 12 Jan 2024 14:14:01 +0100
Subject: [PATCH 12/14] Ignore $scopeinfo in write_json

---
 backends/json/json.cc | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/backends/json/json.cc b/backends/json/json.cc
index fd2c922fd..2f442c494 100644
--- a/backends/json/json.cc
+++ b/backends/json/json.cc
@@ -192,6 +192,10 @@ struct JsonWriter
 		for (auto c : module->cells()) {
 			if (use_selection && !module->selected(c))
 				continue;
+			// Eventually we will want to emit $scopeinfo, but currently this
+			// will break JSON netlist consumers like nextpnr
+			if (c->type == ID($scopeinfo))
+				continue;
 			f << stringf("%s\n", first ? "" : ",");
 			f << stringf("        %s: {\n", get_name(c->name).c_str());
 			f << stringf("          \"hide_name\": %s,\n", c->name[0] == '$' ? "1" : "0");

From 0d5b48de989246ede8554fd93180c5d791e262b1 Mon Sep 17 00:00:00 2001
From: Jannis Harder <me@jix.one>
Date: Mon, 29 Jan 2024 14:46:39 +0100
Subject: [PATCH 13/14] Add scopeinfo index/lookup utils

---
 Makefile            |   3 +-
 kernel/scopeinfo.cc | 129 +++++++++++++
 kernel/scopeinfo.h  | 432 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 563 insertions(+), 1 deletion(-)
 create mode 100644 kernel/scopeinfo.cc
 create mode 100644 kernel/scopeinfo.h

diff --git a/Makefile b/Makefile
index 901106802..ec7454674 100644
--- a/Makefile
+++ b/Makefile
@@ -630,6 +630,7 @@ $(eval $(call add_include_file,kernel/qcsat.h))
 $(eval $(call add_include_file,kernel/register.h))
 $(eval $(call add_include_file,kernel/rtlil.h))
 $(eval $(call add_include_file,kernel/satgen.h))
+$(eval $(call add_include_file,kernel/scopeinfo.h))
 $(eval $(call add_include_file,kernel/sigtools.h))
 $(eval $(call add_include_file,kernel/timinginfo.h))
 $(eval $(call add_include_file,kernel/utils.h))
@@ -656,7 +657,7 @@ $(eval $(call add_include_file,backends/cxxrtl/runtime/cxxrtl/capi/cxxrtl_capi_v
 
 OBJS += kernel/driver.o kernel/register.o kernel/rtlil.o kernel/log.o kernel/calc.o kernel/yosys.o
 OBJS += kernel/binding.o
-OBJS += kernel/cellaigs.o kernel/celledges.o kernel/satgen.o kernel/qcsat.o kernel/mem.o kernel/ffmerge.o kernel/ff.o kernel/yw.o kernel/json.o kernel/fmt.o
+OBJS += kernel/cellaigs.o kernel/celledges.o kernel/satgen.o kernel/scopeinfo.o kernel/qcsat.o kernel/mem.o kernel/ffmerge.o kernel/ff.o kernel/yw.o kernel/json.o kernel/fmt.o
 ifeq ($(ENABLE_ZLIB),1)
 OBJS += kernel/fstdata.o
 endif
diff --git a/kernel/scopeinfo.cc b/kernel/scopeinfo.cc
new file mode 100644
index 000000000..7ed9ebf33
--- /dev/null
+++ b/kernel/scopeinfo.cc
@@ -0,0 +1,129 @@
+/*
+ *  yosys -- Yosys Open SYnthesis Suite
+ *
+ *  Copyright (C) 2024  Jannis Harder <jix@yosyshq.com> <me@jix.one>
+ *
+ *  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/scopeinfo.h"
+
+YOSYS_NAMESPACE_BEGIN
+
+template <typename I, typename Filter> void ModuleHdlnameIndex::index_items(I begin, I end, Filter filter)
+{
+	for (; begin != end; ++begin) {
+		auto const &item = *begin;
+
+		if (!filter(item))
+			continue;
+		std::vector<IdString> path = parse_hdlname(item);
+		if (!path.empty())
+			lookup.emplace(item, tree.insert(path, item));
+	}
+}
+
+void ModuleHdlnameIndex::index()
+{
+	index_wires();
+	index_cells();
+}
+
+void ModuleHdlnameIndex::index_wires()
+{
+	auto wires = module->wires();
+	index_items(wires.begin(), wires.end(), [](Wire *) { return true; });
+}
+
+void ModuleHdlnameIndex::index_cells()
+{
+	auto cells = module->cells();
+	index_items(cells.begin(), cells.end(), [](Cell *) { return true; });
+}
+
+void ModuleHdlnameIndex::index_scopeinfo_cells()
+{
+	auto cells = module->cells();
+	index_items(cells.begin(), cells.end(), [](Cell *cell) { return cell->type == ID($scopeinfo); });
+}
+
+std::vector<std::string> ModuleHdlnameIndex::scope_sources(Cursor cursor)
+{
+	std::vector<std::string> result;
+
+	for (; !cursor.is_root(); cursor = cursor.parent()) {
+		if (!cursor.has_entry()) {
+			result.push_back("");
+			result.push_back("");
+			continue;
+		}
+		Cell *cell = cursor.entry().cell();
+		if (cell == nullptr || cell->type != ID($scopeinfo)) {
+			result.push_back("");
+			result.push_back("");
+			continue;
+		}
+		result.push_back(scopeinfo_get_attribute(cell, ScopeinfoAttrs::Module, ID::src).decode_string());
+		result.push_back(scopeinfo_get_attribute(cell, ScopeinfoAttrs::Cell, ID::src).decode_string());
+	}
+
+	result.push_back(module->get_src_attribute());
+
+	std::reverse(result.begin(), result.end());
+
+	return result;
+}
+
+static const char *attr_prefix(ScopeinfoAttrs attrs)
+{
+	switch (attrs) {
+	case ScopeinfoAttrs::Cell:
+		return "\\cell_";
+	case ScopeinfoAttrs::Module:
+		return "\\module_";
+	default:
+		log_abort();
+	}
+}
+
+bool scopeinfo_has_attribute(const RTLIL::Cell *scopeinfo, ScopeinfoAttrs attrs, const RTLIL::IdString &id)
+{
+	log_assert(scopeinfo->type == ID($scopeinfo));
+	return scopeinfo->has_attribute(attr_prefix(attrs) + RTLIL::unescape_id(id));
+}
+
+RTLIL::Const scopeinfo_get_attribute(const RTLIL::Cell *scopeinfo, ScopeinfoAttrs attrs, const RTLIL::IdString &id)
+{
+	log_assert(scopeinfo->type == ID($scopeinfo));
+	auto found = scopeinfo->attributes.find(attr_prefix(attrs) + RTLIL::unescape_id(id));
+	if (found == scopeinfo->attributes.end())
+		return RTLIL::Const();
+	return found->second;
+}
+
+dict<RTLIL::IdString, RTLIL::Const> scopeinfo_attributes(const RTLIL::Cell *scopeinfo, ScopeinfoAttrs attrs)
+{
+	dict<RTLIL::IdString, RTLIL::Const> attributes;
+
+	const char *prefix = attr_prefix(attrs);
+	int prefix_len = strlen(prefix);
+
+	for (auto const &entry : scopeinfo->attributes)
+		if (entry.first.begins_with(prefix))
+			attributes.emplace(RTLIL::escape_id(entry.first.c_str() + prefix_len), entry.second);
+
+	return attributes;
+}
+
+YOSYS_NAMESPACE_END
diff --git a/kernel/scopeinfo.h b/kernel/scopeinfo.h
new file mode 100644
index 000000000..71af70344
--- /dev/null
+++ b/kernel/scopeinfo.h
@@ -0,0 +1,432 @@
+/*
+ *  yosys -- Yosys Open SYnthesis Suite
+ *
+ *  Copyright (C) 2024  Jannis Harder <jix@yosyshq.com> <me@jix.one>
+ *
+ *  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.
+ *
+ */
+
+#ifndef SCOPEINFO_H
+#define SCOPEINFO_H
+
+#include <vector>
+#include <algorithm>
+
+#include "kernel/yosys.h"
+#include "kernel/celltypes.h"
+
+YOSYS_NAMESPACE_BEGIN
+
+template<typename T>
+class IdTree
+{
+public:
+	struct Cursor;
+
+protected:
+	IdTree *parent = nullptr;
+	IdString scope_name;
+	int depth = 0;
+
+	pool<IdString> names;
+	dict<IdString, T> entries;
+public: // XXX
+	dict<IdString, std::unique_ptr<IdTree>> subtrees;
+
+	template<typename P, typename T_ref>
+	static Cursor do_insert(IdTree *tree, P begin, P end, T_ref &&value)
+	{
+		log_assert(begin != end && "path must be non-empty");
+		while (true) {
+			IdString name = *begin;
+			++begin;
+			log_assert(!name.empty());
+			tree->names.insert(name);
+			if (begin == end) {
+				tree->entries.emplace(name, std::forward<T_ref>(value));
+				return Cursor(tree, name);
+			}
+			auto &unique = tree->subtrees[name];
+			if (!unique) {
+				unique.reset(new IdTree);
+				unique->scope_name = name;
+				unique->parent = tree;
+				unique->depth = tree->depth + 1;
+			}
+			tree = unique.get();
+		}
+	}
+
+public:
+	IdTree() = default;
+	IdTree(const IdTree &) = delete;
+	IdTree(IdTree &&) = delete;
+
+	// A cursor remains valid as long as the (sub-)IdTree it points at is alive
+	struct Cursor
+	{
+		friend class IdTree;
+	protected:
+	public:
+		IdTree *target;
+		IdString scope_name;
+
+		Cursor() : target(nullptr) {}
+		Cursor(IdTree *target, IdString scope_name) : target(target), scope_name(scope_name) {
+			if (scope_name.empty())
+				log_assert(target->parent == nullptr);
+		}
+
+		Cursor do_first_child() {
+			IdTree *tree = nullptr;
+			if (scope_name.empty()) {
+				tree = target;
+			} else {
+				auto found = target->subtrees.find(scope_name);
+				if (found != target->subtrees.end()) {
+					tree = found->second.get();
+				} else {
+					return Cursor();
+				}
+			}
+			if (tree->names.empty()) {
+				return Cursor();
+			}
+			return Cursor(tree, *tree->names.begin());
+		}
+
+		Cursor do_next_sibling() {
+			if (scope_name.empty())
+				return Cursor();
+			auto found = target->names.find(scope_name);
+			if (found == target->names.end())
+				return Cursor();
+			++found;
+			if (found == target->names.end())
+				return Cursor();
+			return Cursor(target, *found);
+		}
+
+		Cursor do_parent() {
+			if (scope_name.empty())
+				return Cursor();
+			if (target->parent != nullptr)
+				return Cursor(target->parent, target->scope_name);
+			return Cursor(target, IdString());
+		}
+
+		Cursor do_next_preorder() {
+			Cursor current = *this;
+			Cursor next = current.do_first_child();
+			if (next.valid())
+				return next;
+			while (current.valid()) {
+				if (next.valid())
+					return next;
+				next = current.do_next_sibling();
+				if (next.valid())
+					return next;
+				current = current.do_parent();
+			}
+			return current;
+		}
+
+		Cursor do_child(IdString name) {
+			IdTree *tree = nullptr;
+			if (scope_name.empty()) {
+				tree = target;
+			} else {
+				auto found = target->subtrees.find(scope_name);
+				if (found != target->subtrees.end()) {
+					tree = found->second.get();
+				} else {
+					return Cursor();
+				}
+			}
+			auto found = tree->names.find(name);
+			if (found == tree->names.end()) {
+				return Cursor();
+			}
+			return Cursor(tree, *found);
+		}
+
+	public:
+		bool operator==(const Cursor &other) const {
+			return target == other.target && scope_name == other.scope_name;
+		}
+		bool operator!=(const Cursor &other) const {
+			return !(*this == other);
+		}
+
+		bool valid() const {
+			return target != nullptr;
+		}
+
+		int depth() const {
+			log_assert(valid());
+			return target->depth + !scope_name.empty();
+		}
+
+		bool is_root() const {
+			return target != nullptr && scope_name.empty();
+		}
+
+		bool has_entry() const {
+			log_assert(valid());
+			return !scope_name.empty() && target->entries.count(scope_name);
+		}
+
+		T &entry() {
+			log_assert(!scope_name.empty());
+			return target->entries.at(scope_name);
+		}
+
+		void assign_path_to(std::vector<IdString> &out_path) {
+			log_assert(valid());
+			out_path.clear();
+			if (scope_name.empty())
+				return;
+			out_path.push_back(scope_name);
+			IdTree *current = target;
+			while (current->parent) {
+				out_path.push_back(current->scope_name);
+				current = current->parent;
+			}
+			std::reverse(out_path.begin(), out_path.end());
+		}
+
+		std::vector<IdString> path() {
+			std::vector<IdString> result;
+			assign_path_to(result);
+			return result;
+		}
+
+		std::string path_str() {
+			std::string result;
+			for (const auto &item : path()) {
+				if (!result.empty())
+					result.push_back(' ');
+				result += RTLIL::unescape_id(item);
+			}
+			return result;
+		}
+
+		Cursor first_child() {
+			log_assert(valid());
+			return do_first_child();
+		}
+
+		Cursor next_preorder() {
+			log_assert(valid());
+			return do_next_preorder();
+		}
+
+		Cursor parent() {
+			log_assert(valid());
+			return do_parent();
+		}
+
+		Cursor child(IdString name) {
+			log_assert(valid());
+			return do_child(name);
+		}
+
+		Cursor common_ancestor(Cursor other) {
+			Cursor current = *this;
+
+			while (current != other) {
+				if (!current.valid() || !other.valid())
+					return Cursor();
+				int delta = current.depth() - other.depth();
+				if (delta >= 0)
+					current = current.do_parent();
+				if (delta <= 0)
+					other = other.do_parent();
+			}
+			return current;
+		}
+	};
+
+	template<typename P>
+	Cursor insert(P begin, P end, const T &value) {
+		return do_insert(this, begin, end, value);
+	}
+
+	template<typename P>
+	Cursor insert(P begin, P end, T &&value) {
+		return do_insert(this, begin, end, std::move(value));
+	}
+
+	template<typename P>
+	Cursor insert(const P &path, const T &value) {
+		return do_insert(this, path.begin(), path.end(), value);
+	}
+
+	template<typename P>
+	Cursor insert(const P &path, T &&value) {
+		return do_insert(this, path.begin(), path.end(), std::move(value));
+	}
+
+	Cursor cursor() {
+		return parent ? Cursor(this->parent, this->scope_name) : Cursor(this, IdString());
+	}
+
+	template<typename P>
+	Cursor cursor(P begin, P end) {
+		Cursor current = cursor();
+		for (; begin != end; ++begin) {
+			current = current.do_child(*begin);
+			if (!current.valid())
+				break;
+		}
+		return current;
+	}
+
+	template<typename P>
+	Cursor cursor(const P &path) {
+		return cursor(path.begin(), path.end());
+	}
+};
+
+
+struct ModuleItem {
+	enum class Type {
+		Wire,
+		Cell,
+	};
+	Type type;
+	void *ptr;
+
+	ModuleItem(Wire *wire) : type(Type::Wire), ptr(wire) {}
+	ModuleItem(Cell *cell) : type(Type::Cell), ptr(cell) {}
+
+	bool is_wire() const { return type == Type::Wire; }
+	bool is_cell() const { return type == Type::Cell; }
+
+	Wire *wire() const { return type == Type::Wire ? static_cast<Wire *>(ptr) : nullptr; }
+	Cell *cell() const { return type == Type::Cell ? static_cast<Cell *>(ptr) : nullptr; }
+
+	bool operator==(const ModuleItem &other) const { return ptr == other.ptr && type == other.type; }
+	unsigned int hash() const { return (uintptr_t)ptr; }
+};
+
+static inline void log_dump_val_worker(typename IdTree<ModuleItem>::Cursor cursor ) { log("%p %s", cursor.target, log_id(cursor.scope_name)); }
+
+template<typename T>
+static inline void log_dump_val_worker(const typename std::unique_ptr<T> &cursor ) { log("unique %p", cursor.get()); }
+
+template<typename O>
+std::vector<IdString> parse_hdlname(const O* object)
+{
+	std::vector<IdString> path;
+	if (!object->name.isPublic())
+		return path;
+	for (auto const &item : object->get_hdlname_attribute())
+		path.push_back("\\" + item);
+	if (path.empty())
+		path.push_back(object->name);
+	return path;
+}
+
+template<typename O>
+std::pair<std::vector<IdString>, IdString> parse_scopename(const O* object)
+{
+	std::vector<IdString> path;
+	IdString trailing = object->name;
+	if (object->name.isPublic()) {
+		for (auto const &item : object->get_hdlname_attribute())
+			path.push_back("\\" + item);
+		if (!path.empty()) {
+			trailing = path.back();
+			path.pop_back();
+		}
+	} else {
+		for (auto const &item : split_tokens(object->get_string_attribute(ID(scopename)), " "))
+			path.push_back("\\" + item);
+
+	}
+	return {path, trailing};
+}
+
+struct ModuleHdlnameIndex {
+	typedef IdTree<ModuleItem>::Cursor Cursor;
+
+	RTLIL::Module *module;
+	IdTree<ModuleItem> tree;
+	dict<ModuleItem, Cursor> lookup;
+
+	ModuleHdlnameIndex(RTLIL::Module *module) : module(module) {}
+
+private:
+	template<typename I, typename Filter>
+	void index_items(I begin, I end, Filter filter);
+
+public:
+	// Index all wires and cells of the module
+	void index();
+
+	// Index all wires of the module
+	void index_wires();
+
+	// Index all cells of the module
+	void index_cells();
+
+	// Index only the $scopeinfo cells of the module.
+	// This is sufficient when using `containing_scope`.
+	void index_scopeinfo_cells();
+
+
+	// Return the cursor for the containing scope of some RTLIL object (Wire/Cell/...)
+	template<typename O>
+	std::pair<Cursor, IdString> containing_scope(O *object) {
+		auto pair = parse_scopename(object);
+		return {tree.cursor(pair.first), pair.second};
+	}
+
+	// Return a vector of source locations starting from the indexed module to
+	// the scope represented by the cursor. The vector alternates module and
+	// module item source locations, using empty strings for missing src
+	// attributes.
+	std::vector<std::string> scope_sources(Cursor cursor);
+
+	// Return a vector of source locations starting from the indexed module to
+	// the passed RTLIL object (Wire/Cell/...). The vector alternates module
+	// and module item source locations, using empty strings for missing src
+	// attributes.
+	template<typename O>
+	std::vector<std::string> sources(O *object) {
+		auto pair = parse_scopename(object);
+		std::vector<std::string> result = scope_sources(tree.cursor(pair.first));
+		result.push_back(object->get_src_attribute());
+		return result;
+	}
+};
+
+enum class ScopeinfoAttrs {
+	Module,
+	Cell,
+};
+
+// Check whether the flattened module or flattened cell corresponding to a $scopeinfo cell had a specific attribute.
+bool scopeinfo_has_attribute(const RTLIL::Cell *scopeinfo, ScopeinfoAttrs attrs, const RTLIL::IdString &id);
+
+// Get a specific attribute from the flattened module or flattened cell corresponding to a $scopeinfo cell.
+RTLIL::Const scopeinfo_get_attribute(const RTLIL::Cell *scopeinfo, ScopeinfoAttrs attrs, const RTLIL::IdString &id);
+
+// Get all attribute from the flattened module or flattened cell corresponding to a $scopeinfo cell.
+dict<RTLIL::IdString, RTLIL::Const> scopeinfo_attributes(const RTLIL::Cell *scopeinfo, ScopeinfoAttrs attrs);
+
+YOSYS_NAMESPACE_END
+
+#endif

From 364bcfb8f1accacda63efa0e57c777c70ad5a7bc Mon Sep 17 00:00:00 2001
From: Jannis Harder <me@jix.one>
Date: Mon, 29 Jan 2024 21:28:51 +0100
Subject: [PATCH 14/14] Example pass for the scopeinfo index/lookup utils

---
 examples/cxx-api/scopeinfo_example.cc | 144 ++++++++++++++++++++++++++
 1 file changed, 144 insertions(+)
 create mode 100644 examples/cxx-api/scopeinfo_example.cc

diff --git a/examples/cxx-api/scopeinfo_example.cc b/examples/cxx-api/scopeinfo_example.cc
new file mode 100644
index 000000000..f163dff9e
--- /dev/null
+++ b/examples/cxx-api/scopeinfo_example.cc
@@ -0,0 +1,144 @@
+/*
+ *  yosys -- Yosys Open SYnthesis Suite
+ *
+ *  Copyright (C) 2023  Jannis Harder <jix@yosyshq.com> <me@jix.one>
+ *
+ *  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.
+ *
+ */
+
+// build: yosys-config --build scopeinfo_example.so scopeinfo_example.cc
+// use: yosys -m scopeinfo_example.so
+
+#include "backends/rtlil/rtlil_backend.h"
+#include "kernel/scopeinfo.h"
+#include "kernel/yosys.h"
+
+USING_YOSYS_NAMESPACE
+PRIVATE_NAMESPACE_BEGIN
+
+struct ScopeinfoExamplePass : public Pass {
+	ScopeinfoExamplePass() : Pass("scopeinfo_example", "dump scopeinfo") {}
+	void help() override
+	{
+		//   |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
+		log("\n");
+		log("    scopeinfo_example [options] [selection]\n");
+		log("\n");
+	}
+
+	void execute(std::vector<std::string> args, RTLIL::Design *design) override
+	{
+		log_header(design, "Executing SCOPEINFO_EXAMPLE pass.\n");
+
+		bool do_wires = false;
+		bool do_common = false;
+
+		size_t argidx;
+		for (argidx = 1; argidx < args.size(); argidx++) {
+			if (args[argidx] == "-wires") {
+				do_wires = true;
+				continue;
+			}
+			if (args[argidx] == "-common") {
+				do_common = true;
+				continue;
+			}
+			break;
+		}
+		extra_args(args, argidx, design);
+
+
+		if (do_wires) {
+			for (auto module : design->selected_modules()) {
+				log("Source hierarchy for all selected wires within %s:\n", log_id(module));
+				ModuleHdlnameIndex index(module);
+
+				index.index_scopeinfo_cells();
+
+				for (auto wire : module->selected_wires()) {
+					if (!wire->name.isPublic())
+						continue;
+
+					auto wire_scope = index.containing_scope(wire);
+
+					if (!wire_scope.first.valid()) {
+						log_warning("Couldn't find containing scope for %s in index\n", log_id(wire));
+						continue;
+					}
+
+					log("%s %s\n", wire_scope.first.path_str().c_str(), log_id(wire_scope.second));
+					for (auto src : index.sources(wire))
+						log(" - %s\n", src.c_str());
+				}
+			}
+		}
+
+		if (do_common) {
+			for (auto module : design->selected_modules()) {
+				std::vector<Wire *> wires = module->selected_wires();
+
+				// Shuffle wires so this example produces more interesting outputs
+				std::sort(wires.begin(), wires.end(), [](Wire *a, Wire *b) {
+					return mkhash_xorshift(a->name.hash() * 0x2c9277b5) < mkhash_xorshift(b->name.hash() * 0x2c9277b5);
+				});
+
+				ModuleHdlnameIndex index(module);
+
+				index.index_scopeinfo_cells();
+
+				for (auto wire_i = wires.begin(), wire_end = wires.end(); wire_i != wire_end; ++wire_i) {
+					if (!(*wire_i)->name.isPublic())
+						continue;
+
+					std::pair<ModuleHdlnameIndex::Cursor, IdString> scope_i = index.containing_scope(*wire_i);
+					if (!scope_i.first.valid())
+						continue;
+
+					int limit = 0;
+
+					for (auto wire_j = wire_i + 1; wire_j != wire_end; ++wire_j) {
+						if (!(*wire_j)->name.isPublic())
+							continue;
+
+						std::pair<ModuleHdlnameIndex::Cursor, IdString> scope_j = index.containing_scope(*wire_j);
+						if (!scope_j.first.valid())
+							continue;
+
+						// Skip wires in the same hierarchy level
+						if (scope_i.first == scope_j.first)
+							continue;
+
+
+						ModuleHdlnameIndex::Cursor common = scope_i.first.common_ancestor(scope_j.first);
+
+						// Try to show at least some non-root common ancestors
+						if (common.is_root() && limit > 5)
+							continue;
+
+						log("common_ancestor(%s %s%s%s, %s %s%s%s) = %s %s\n",
+							log_id(module), scope_i.first.path_str().c_str(), scope_i.first.is_root() ? "" : " ", log_id(scope_i.second),
+							log_id(module), scope_j.first.path_str().c_str(), scope_j.first.is_root() ? "" : " ", log_id(scope_j.second),
+							log_id(module), common.path_str().c_str()
+						);
+
+						if (++limit == 10)
+							break;
+					}
+				}
+			}
+		}
+	}
+} ScopeinfoExamplePass;
+
+PRIVATE_NAMESPACE_END