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