From f7d1a491d7c4d172ea1f10d234728899cf69f2c8 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Thu, 6 Aug 2026 16:16:22 +0200 Subject: [PATCH] tests: cover twines, name publicity, hashing and sorting --- tests/rtlil/roundtrip-suffix.sh | 23 ++ tests/rtlil/suffix-chain.il | 13 ++ tests/rtlil/suffix-twines.il | 12 ++ tests/rtlil/twines-forward-compat.sh | 38 ++++ tests/unit/kernel/CMakeLists.txt | 1 + tests/unit/kernel/flattenPerfTest.cc | 184 ++++++++++++++++ tests/unit/kernel/nameMasqTest.cc | 263 +++++++++++++++++++++++ tests/unit/kernel/pooledNameTest.cc | 84 ++++++++ tests/unit/kernel/rtlilTest.cc | 51 ++--- tests/unit/kernel/twineHashTest.cc | 90 ++++++++ tests/unit/kernel/twinePublicityTest.cc | 167 ++++++++++++++ tests/unit/kernel/twineSortTest.cc | 126 +++++++++++ tests/unit/yosysSetupEnv.cc | 11 + tests/various/design_equal_pools.ys | 50 +++++ tests/various/design_equal_pools_fail.ys | 22 ++ 15 files changed, 1105 insertions(+), 30 deletions(-) create mode 100644 tests/rtlil/roundtrip-suffix.sh create mode 100644 tests/rtlil/suffix-chain.il create mode 100644 tests/rtlil/suffix-twines.il create mode 100644 tests/rtlil/twines-forward-compat.sh create mode 100644 tests/unit/kernel/flattenPerfTest.cc create mode 100644 tests/unit/kernel/nameMasqTest.cc create mode 100644 tests/unit/kernel/pooledNameTest.cc create mode 100644 tests/unit/kernel/twineHashTest.cc create mode 100644 tests/unit/kernel/twinePublicityTest.cc create mode 100644 tests/unit/kernel/twineSortTest.cc create mode 100644 tests/unit/yosysSetupEnv.cc create mode 100644 tests/various/design_equal_pools.ys create mode 100644 tests/various/design_equal_pools_fail.ys diff --git a/tests/rtlil/roundtrip-suffix.sh b/tests/rtlil/roundtrip-suffix.sh new file mode 100644 index 000000000..320ea941b --- /dev/null +++ b/tests/rtlil/roundtrip-suffix.sh @@ -0,0 +1,23 @@ +set -euo pipefail + +mkdir -p temp + +${YOSYS} -p "read_rtlil suffix-twines.il; write_rtlil temp/suffix-twines-write.il" +tail -n +2 temp/suffix-twines-write.il > temp/suffix-twines-write-nogen.il +diff suffix-twines.il temp/suffix-twines-write-nogen.il + +${YOSYS} -p "read_rtlil suffix-twines.il; design -push; design -pop; write_rtlil temp/suffix-twines-push.il" +tail -n +2 temp/suffix-twines-push.il > temp/suffix-twines-push-nogen.il +diff suffix-twines.il temp/suffix-twines-push-nogen.il + +${YOSYS} -p "read_rtlil suffix-chain.il; design -push; design -pop; write_rtlil temp/suffix-chain-push.il" +tail -n +2 temp/suffix-chain-push.il > temp/suffix-chain-push-nogen.il +diff suffix-chain.il temp/suffix-chain-push-nogen.il + +${YOSYS} -p "read_rtlil suffix-chain.il; opt_clean; write_rtlil -readable temp/suffix-chain-gc-resolved.il" +grep 'wire' temp/suffix-chain-gc-resolved.il | sort > temp/suffix-chain-gc-resolved.names +cat > temp/suffix-chain-expected.names < temp/moved-static.il < temp/stale-dynamic.il < + +#include +#include +#include +#include + +#include + +#include "kernel/rtlil.h" +#include "kernel/yosys.h" + +YOSYS_NAMESPACE_BEGIN + +namespace { + +int env_int(const char *name, int fallback) +{ + const char *value = getenv(name); + if (value == nullptr || *value == 0) + return fallback; + return atoi(value); +} + +size_t rss_bytes() +{ + size_t total_pages = 0, resident_pages = 0; + FILE *f = fopen("/proc/self/statm", "r"); + if (f == nullptr) + return 0; + if (fscanf(f, "%zu %zu", &total_pages, &resident_pages) != 2) + resident_pages = 0; + fclose(f); + return resident_pages * (size_t)sysconf(_SC_PAGESIZE); +} + +size_t peak_rss_bytes() +{ + struct rusage ru; + if (getrusage(RUSAGE_SELF, &ru) != 0) + return 0; + return (size_t)ru.ru_maxrss * 1024; +} + +std::string name_padding; +bool attach_src = false; +int src_counter = 0; + +void set_src(RTLIL::AttrObject *object) +{ + if (!attach_src) + return; + src_counter++; + object->set_src_attribute(stringf("flatten_perf_source_file.v:%d.1-%d.20", src_counter, src_counter)); +} + +std::string pad(std::string name) +{ + name += name_padding; + return name; +} + +double ms(std::chrono::steady_clock::duration d) +{ + return std::chrono::duration(d).count(); +} + +RTLIL::Module *build_leaf(RTLIL::Design *design, int chain_length) +{ + RTLIL::Module *m = design->addModule(pad("\\flatten_perf_leaf_module")); + + RTLIL::Wire *in = m->addWire(pad("\\leaf_module_data_input_port")); + in->port_input = true; + set_src(in); + RTLIL::Wire *out = m->addWire(pad("\\leaf_module_data_output_port")); + out->port_output = true; + set_src(out); + m->fixup_ports(); + + RTLIL::SigBit prev = in; + for (int i = 0; i < chain_length; i++) { + RTLIL::SigBit next = i + 1 == chain_length + ? RTLIL::SigBit(out) + : RTLIL::SigBit(m->addWire(pad(stringf("\\leaf_intermediate_signal_wire_number_%d", i)))); + if (next.wire != nullptr) + set_src(next.wire); + set_src(m->addNotGate(pad(stringf("$leaf_inverter_cell_instance_number_%d", i)), prev, next)); + prev = next; + } + return m; +} + +RTLIL::Module *build_level(RTLIL::Design *design, RTLIL::Module *child, int level, int branch) +{ + RTLIL::Module *m = design->addModule(pad(stringf("\\flatten_perf_hierarchy_level_%d_module", level))); + + RTLIL::Wire *in = m->addWire(pad(stringf("\\level_%d_module_data_input_port", level))); + in->port_input = true; + set_src(in); + RTLIL::Wire *out = m->addWire(pad(stringf("\\level_%d_module_data_output_port", level))); + out->port_output = true; + set_src(out); + m->fixup_ports(); + + RTLIL::Wire *child_in = child->wire(child->ports.at(0)); + RTLIL::Wire *child_out = child->wire(child->ports.at(1)); + + RTLIL::SigBit prev = in; + for (int k = 0; k < branch; k++) { + RTLIL::SigBit next = k + 1 == branch + ? RTLIL::SigBit(out) + : RTLIL::SigBit(m->addWire(pad(stringf("\\level_%d_interconnect_signal_wire_number_%d", level, k)))); + RTLIL::Cell *cell = m->addCell( + pad(stringf("\\hierarchical_child_instance_at_level_%d_branch_%d", level, k)), child->name); + set_src(cell); + cell->setPort(child_in->name, prev); + cell->setPort(child_out->name, next); + prev = next; + } + return m; +} + +RTLIL::Module *build_design(RTLIL::Design *design, int depth, int branch, int chain_length) +{ + RTLIL::Module *m = build_leaf(design, chain_length); + for (int level = depth - 1; level >= 0; level--) + m = build_level(design, m, level, branch); + m->set_bool_attribute(ID::top); + return m; +} + +} // namespace + +TEST(FlattenPerf, deep_hierarchy_stress) +{ + int depth = env_int("YOSYS_FLATTEN_PERF_DEPTH", 8); + int branch = env_int("YOSYS_FLATTEN_PERF_BRANCH", 2); + int chain_length = env_int("YOSYS_FLATTEN_PERF_CHAIN", 48); + int extra_name_chars = env_int("YOSYS_FLATTEN_PERF_NAMEPAD", 32); + const char *flatten_args = getenv("YOSYS_FLATTEN_PERF_ARGS"); + + ASSERT_GE(depth, 1); + ASSERT_GE(branch, 2); + ASSERT_GE(chain_length, 1); + ASSERT_GE(extra_name_chars, 0); + + name_padding = std::string(extra_name_chars, 'n'); + attach_src = env_int("YOSYS_FLATTEN_PERF_SRC", 0) != 0; + + RTLIL::Design *design = new RTLIL::Design; + + auto build_start = std::chrono::steady_clock::now(); + RTLIL::Module *top = build_design(design, depth, branch, chain_length); + auto build_end = std::chrono::steady_clock::now(); + + size_t rss_before = rss_bytes(); + + auto flatten_start = std::chrono::steady_clock::now(); + Pass::call(design, flatten_args != nullptr ? std::string("flatten ") + flatten_args : std::string("flatten")); + auto flatten_end = std::chrono::steady_clock::now(); + + size_t rss_after = rss_bytes(); + + size_t leaves = 1; + for (int level = 0; level < depth; level++) + leaves *= (size_t)branch; + + EXPECT_EQ(design->modules().size(), 1u); + EXPECT_EQ(design->top_module(), top); + EXPECT_GE(top->cells().size(), leaves * (size_t)chain_length); + + printf("[ PERF ] depth=%d branch=%d chain=%d namepad=%d src=%d args=%s leaves=%zu\n", depth, branch, chain_length, + extra_name_chars, (int)attach_src, flatten_args != nullptr ? flatten_args : "", leaves); + printf("[ PERF ] cells=%zu wires=%zu\n", top->cells().size(), top->wires().size()); + printf("[ PERF ] build_ms=%.1f flatten_ms=%.1f\n", ms(build_end - build_start), ms(flatten_end - flatten_start)); + printf("[ PERF ] rss_before_mb=%.1f rss_after_mb=%.1f flatten_rss_mb=%.1f peak_rss_mb=%.1f\n", + rss_before / 1048576.0, rss_after / 1048576.0, + (rss_after - rss_before) / 1048576.0, peak_rss_bytes() / 1048576.0); + fflush(stdout); + + delete design; +} + +YOSYS_NAMESPACE_END diff --git a/tests/unit/kernel/nameMasqTest.cc b/tests/unit/kernel/nameMasqTest.cc new file mode 100644 index 000000000..f53e1e1bf --- /dev/null +++ b/tests/unit/kernel/nameMasqTest.cc @@ -0,0 +1,263 @@ +#include + +#include "kernel/rtlil.h" +#include "kernel/yosys.h" + +YOSYS_NAMESPACE_BEGIN + +namespace { + +struct MasqFixture { + Design design; + Module *mod; + Wire *wire; + Cell *cell; + + MasqFixture() + { + mod = design.addModule(std::string("\\zz_top")); + wire = mod->addWire(std::string("\\zz_alu_result"), 4); + cell = mod->addCell(std::string("\\zz_adder"), ID($and)); + } +}; + +} + +TEST(NameMasqTest, StringQueries) +{ + MasqFixture f; + const RTLIL::WireNameMasq &name = f.wire->name; + + EXPECT_TRUE(name.begins_with("\\zz_alu")); + EXPECT_FALSE(name.begins_with("\\zz_alv")); + EXPECT_TRUE(name.ends_with("result")); + EXPECT_FALSE(name.ends_with("resulz")); + EXPECT_TRUE(name.contains("alu")); + EXPECT_FALSE(name.contains("mul")); + EXPECT_EQ(name.substr(1, 2), "zz"); + EXPECT_EQ(name.substr(), name.escaped()); + EXPECT_EQ(name[0], '\\'); + EXPECT_EQ(name.size(), name.escaped().size()); + EXPECT_FALSE(name.empty()); +} + +TEST(NameMasqTest, SuffixQueries) +{ + Design design; + Module *mod = design.addModule(std::string("\\zz_top")); + IdString prefix = design.twines.add(std::string("\\zz_bus")); + IdString suffixed = design.twines.add(Twine{Twine::Suffix{prefix, "_hi"}}); + Wire *w = mod->addWire(suffixed, 1); + + EXPECT_EQ(w->name.escaped(), "\\zz_bus_hi"); + EXPECT_TRUE(w->name.begins_with("\\zz_bus")); + EXPECT_TRUE(w->name.begins_with("\\zz_bus_h")); + EXPECT_TRUE(w->name.begins_with("\\zz_bus_hi")); + EXPECT_FALSE(w->name.begins_with("\\zz_bus_hi_")); + EXPECT_FALSE(w->name.begins_with("\\zz_buT")); + EXPECT_FALSE(w->name.begins_with("\\zz_bus_i")); + EXPECT_TRUE(w->name.ends_with("_hi")); + EXPECT_EQ(w->name.size(), w->name.escaped().size()); + EXPECT_EQ(w->name.substr(4), "bus_hi"); +} + +TEST(NameMasqTest, PoollessMatchesPooled) +{ + Design design; + IdString id = design.twines.add(std::string("\\zz_shared")); + PooledName pooled(&design.twines, id); + PooledName poolless(ID::A); + + EXPECT_EQ(pooled.pool(), &design.twines); + EXPECT_EQ(poolless.pool(), nullptr); + + EXPECT_EQ(poolless.escaped(), "\\A"); + EXPECT_EQ(poolless.size(), poolless.escaped().size()); + EXPECT_TRUE(poolless.begins_with("\\A")); + EXPECT_FALSE(poolless.begins_with("\\AB")); + EXPECT_TRUE(poolless.ends_with("A")); + + PooledName same_content(&design.twines, design.twines.add(std::string("\\A"))); + EXPECT_EQ(same_content.escaped(), poolless.escaped()); + EXPECT_EQ(same_content.size(), poolless.size()); + EXPECT_EQ(same_content.begins_with("\\A"), poolless.begins_with("\\A")); +} + +TEST(NameMasqTest, PoollessOrdering) +{ + PooledName a(ID::A); + PooledName b(ID::B); + + EXPECT_EQ(a.pool(), nullptr); + EXPECT_TRUE(a.lt_by_name(b)); + EXPECT_FALSE(b.lt_by_name(a)); + EXPECT_FALSE(a.lt_by_name(a)); +} + +TEST(NameMasqTest, PooledOrdering) +{ + Design design; + Module *mod = design.addModule(std::string("\\zz_top")); + Wire *first = mod->addWire(std::string("\\zz_aaa"), 1); + Wire *second = mod->addWire(std::string("\\zz_bbb"), 1); + + EXPECT_TRUE(first->name.lt_by_name(second->name)); + EXPECT_FALSE(second->name.lt_by_name(first->name)); + EXPECT_FALSE(first->name.lt_by_name(first->name)); + + EXPECT_NE(first->name < second->name, second->name < first->name); + EXPECT_FALSE(first->name < first->name); +} + +TEST(NameMasqTest, Membership) +{ + MasqFixture f; + + EXPECT_TRUE(f.cell->type.in(ID($and))); + EXPECT_FALSE(f.cell->type.in(ID($or))); + EXPECT_TRUE(f.cell->type.in(ID($or), ID($and))); + EXPECT_FALSE(f.cell->type.in(ID($or), ID($xor))); + EXPECT_TRUE(f.wire->name.in(IdString(f.wire->name))); +} + +TEST(NameMasqTest, Equality) +{ + MasqFixture f; + IdString id = f.wire->name; + + EXPECT_TRUE(f.wire->name == id); + EXPECT_TRUE(id == f.wire->name); + EXPECT_FALSE(f.wire->name != id); + EXPECT_TRUE(f.wire->name != ID::A); + + EXPECT_TRUE(f.cell->type == ID($and)); + EXPECT_TRUE(f.cell->type != ID($or)); + + EXPECT_TRUE(f.wire->name == std::string("\\zz_alu_result")); + EXPECT_TRUE(f.wire->name != std::string("\\zz_other")); + + EXPECT_TRUE(f.wire->name == f.wire->name); + EXPECT_TRUE(f.wire->name != f.cell->name); + + PooledName null_name; + EXPECT_TRUE(null_name == IdString::Null); + EXPECT_TRUE(null_name.empty()); + EXPECT_FALSE(f.wire->name == IdString::Null); + EXPECT_TRUE(f.wire->name != IdString::Null); +} + +TEST(NameMasqTest, CrossMasqEquality) +{ + Design design; + Module *mod = design.addModule(std::string("\\zz_top")); + Cell *recursive = mod->addCell(std::string("\\zz_self"), mod->name); + Cell *other = mod->addCell(std::string("\\zz_and"), ID($and)); + + EXPECT_TRUE(mod->name == recursive->type); + EXPECT_TRUE(recursive->type == mod->name); + EXPECT_FALSE(mod->name == other->type); + EXPECT_TRUE(mod->name != other->type); + EXPECT_TRUE(recursive->name != other->name); +} + +TEST(NameMasqTest, Conversions) +{ + MasqFixture f; + + std::string as_string = f.wire->name; + IdString as_id = f.wire->name; + + EXPECT_EQ(as_string, "\\zz_alu_result"); + EXPECT_EQ(as_string, f.wire->name.str()); + EXPECT_EQ(as_id, f.wire->name.ref()); + EXPECT_EQ(f.wire->name.pool(), &f.design.twines); + EXPECT_EQ(f.cell->type.pool(), &f.design.twines); + EXPECT_EQ(f.mod->name.pool(), &f.design.twines); +} + +TEST(NameMasqTest, Hashing) +{ + MasqFixture f; + + EXPECT_EQ(run_hash(f.wire->name), run_hash(f.wire->name.ref())); + EXPECT_EQ(run_hash(f.cell->type), run_hash(f.cell->type.ref())); + EXPECT_EQ(run_hash(PooledName(f.wire->name)), run_hash(f.wire->name.ref())); + EXPECT_NE(run_hash(f.wire->name), run_hash(f.cell->name.ref())); +} + +TEST(NameMasqTest, WireAssignment) +{ + Design design; + Module *mod = design.addModule(std::string("\\zz_top")); + Wire *a = mod->addWire(std::string("\\zz_a"), 1); + Wire *b = mod->addWire(std::string("\\zz_b"), 1); + IdString renamed = design.twines.add(std::string("\\zz_renamed")); + + a->name = renamed; + EXPECT_EQ(a->name.str(), "\\zz_renamed"); + + b->name = a->name; + EXPECT_EQ(b->name.str(), "\\zz_renamed"); + EXPECT_TRUE(a->name == b->name); + + Wire *c = mod->addWire(std::string("\\zz_c"), 1); + c->name = std::move(b->name); + EXPECT_EQ(c->name.str(), "\\zz_renamed"); +} + +TEST(NameMasqTest, TypeAssignment) +{ + MasqFixture f; + Cell *other = f.mod->addCell(std::string("\\zz_other"), ID($or)); + + f.cell->type = ID($xor); + EXPECT_TRUE(f.cell->type == ID($xor)); + EXPECT_EQ(f.cell->type.str(), "$xor"); + + other->type = f.cell->type; + EXPECT_TRUE(other->type == ID($xor)); + + Cell *third = f.mod->addCell(std::string("\\zz_third"), ID($not)); + third->type = std::move(other->type); + EXPECT_TRUE(third->type == ID($xor)); +} + +TEST(NameMasqTest, ModuleAssignment) +{ + Design design; + Module *mod = design.addModule(std::string("\\zz_top")); + Module *sibling = design.addModule(std::string("\\zz_side")); + IdString renamed = design.twines.add(std::string("\\zz_renamed")); + + mod->name = renamed; + EXPECT_EQ(mod->name.str(), "\\zz_renamed"); + EXPECT_TRUE(mod->name == renamed); + + sibling->name = mod->name; + EXPECT_EQ(sibling->name.str(), "\\zz_renamed"); + + EXPECT_EQ(PooledName(mod->name).str(), "\\zz_renamed"); +} + +TEST(NameMasqTest, MakePair) +{ + MasqFixture f; + + auto left = make_pair(f.wire->name, 7); + auto right = make_pair(7, f.wire->name); + auto both = make_pair(f.wire->name, f.cell->type); + + EXPECT_TRUE((std::is_same_v)); + EXPECT_TRUE((std::is_same_v)); + EXPECT_TRUE((std::is_same_v)); + EXPECT_TRUE((std::is_same_v)); + + EXPECT_EQ(left.first, f.wire->name.ref()); + EXPECT_EQ(left.second, 7); + EXPECT_EQ(right.first, 7); + EXPECT_EQ(right.second, f.wire->name.ref()); + EXPECT_EQ(both.first, f.wire->name.ref()); + EXPECT_EQ(both.second, f.cell->type.ref()); +} + +YOSYS_NAMESPACE_END diff --git a/tests/unit/kernel/pooledNameTest.cc b/tests/unit/kernel/pooledNameTest.cc new file mode 100644 index 000000000..ab2238651 --- /dev/null +++ b/tests/unit/kernel/pooledNameTest.cc @@ -0,0 +1,84 @@ +#include + +#include "kernel/rtlil.h" +#include "kernel/yosys.h" + +YOSYS_NAMESPACE_BEGIN + +TEST(PooledNameTest, OwnPool) +{ + TwinePool pool; + IdString pub = pool.add(std::string("\\zz_alpha")); + IdString priv = pool.add(std::string("$zz_beta")); + + PooledName a(&pool, pub); + PooledName b(&pool, priv); + + EXPECT_EQ(a.str(), "\\zz_alpha"); + EXPECT_EQ(a.unescape(), "zz_alpha"); + EXPECT_TRUE(a.isPublic()); + EXPECT_EQ(b.str(), "$zz_beta"); + EXPECT_EQ(b.unescape(), "$zz_beta"); + EXPECT_FALSE(b.isPublic()); +} + +TEST(PooledNameTest, DivergedPools) +{ + TwinePool pool_a; + TwinePool pool_b; + pool_b.add(std::string("\\zz_filler")); + + IdString in_a = pool_a.add(std::string("\\zz_shared")); + IdString in_b = pool_b.add(std::string("\\zz_shared")); + ASSERT_NE(in_a, in_b); + + PooledName a(&pool_a, in_a); + PooledName b(&pool_b, in_b); + + EXPECT_EQ(a.str(), b.str()); + EXPECT_NE(a.ref(), b.ref()); + EXPECT_FALSE(a == b); +} + +TEST(PooledNameTest, PoollessConstids) +{ + PooledName kind(ID($state)); + EXPECT_EQ(kind.str(), "$state"); + EXPECT_EQ(kind.unescape(), "$state"); + EXPECT_TRUE(kind == ID($state)); +} + +TEST(PooledNameTest, PoollessLookup) +{ + TwinePool pool; + IdString ref = pool.add(std::string("\\zz_gamma")); + + dict by_name; + by_name[PooledName(&pool, ref)] = 7; + + EXPECT_EQ(by_name.at(PooledName(ref)), 7); + EXPECT_EQ(by_name.at(PooledName(&pool, ref)), 7); +} + +TEST(PooledNameTest, FromMasqs) +{ + Design design; + Module *mod = design.addModule(std::string("\\zz_top")); + Wire *w = mod->addWire(std::string("\\zz_wire"), 1); + Cell *cell = mod->addCell(std::string("\\zz_cell"), ID($and)); + + EXPECT_EQ(PooledName(mod->name).str(), "\\zz_top"); + EXPECT_EQ(PooledName(w->name).str(), "\\zz_wire"); + EXPECT_EQ(PooledName(cell->name).unescape(), "zz_cell"); + EXPECT_EQ(PooledName(cell->type).str(), "$and"); +} + +TEST(PooledNameTest, NullName) +{ + PooledName none; + EXPECT_TRUE(none.empty()); + EXPECT_EQ(none.str(), ""); + EXPECT_EQ(none.unescape(), ""); +} + +YOSYS_NAMESPACE_END diff --git a/tests/unit/kernel/rtlilTest.cc b/tests/unit/kernel/rtlilTest.cc index 5de5ca339..708aa4a28 100644 --- a/tests/unit/kernel/rtlilTest.cc +++ b/tests/unit/kernel/rtlilTest.cc @@ -22,7 +22,7 @@ namespace RTLIL { if (log_files.empty()) log_files.emplace_back(stdout); } virtual void SetUp() override { - IdString::ensure_prepopulated(); + StaticTwines::init(); } }; @@ -335,9 +335,10 @@ namespace RTLIL { } TEST_F(KernelRtlilTest, ModuleAddWireWidthLimit) { - std::unique_ptr mod = std::make_unique(); - EXPECT_DEATH(mod->addWire(ID(test), RTLIL::WIDTH_LIMIT), ""); - EXPECT_NO_FATAL_FAILURE(mod->addWire(ID(test), RTLIL::WIDTH_LIMIT - 1)); + std::unique_ptr design = std::make_unique(); + Module *mod = design->addModule("$test_mod"); + EXPECT_DEATH(mod->addWire("\\test", RTLIL::WIDTH_LIMIT), ""); + EXPECT_NO_FATAL_FAILURE(mod->addWire("\\test2", RTLIL::WIDTH_LIMIT - 1)); } TEST_F(KernelRtlilTest, ConstEqualStr) { @@ -417,34 +418,23 @@ namespace RTLIL { EXPECT_FALSE(Const().is_onehot(&pos)); } - TEST_F(KernelRtlilTest, OwningIdString) { - OwningIdString own("\\figblortle"); - OwningIdString::collect_garbage(); - EXPECT_EQ(own.str(), "\\figblortle"); - } - TEST_F(KernelRtlilTest, LookupAutoidxId) { - IdString id = NEW_ID; - IdString id2 = IdString(id.str()); - EXPECT_EQ(id, id2); + TwinePool twines; + IdString id = twines.add(NEW_ID); + TwineSearch search(&twines); + EXPECT_EQ(id, search.find(twines.str(id))); + EXPECT_EQ(twines.find(twines.str(id)), IdString::Null); } TEST_F(KernelRtlilTest, NewIdBeginsWith) { - IdString id = NEW_ID; - EXPECT_TRUE(id.begins_with("$auto")); - EXPECT_FALSE(id.begins_with("xyz")); - EXPECT_TRUE(id.begins_with("$auto$")); - EXPECT_FALSE(id.begins_with("abcdefghijklmn")); - EXPECT_TRUE(id.begins_with("$auto$rtlilTest")); - EXPECT_FALSE(id.begins_with("$auto$rtlilX")); - } - - TEST_F(KernelRtlilTest, NewIdIndexing) { - IdString id = NEW_ID; - std::string str = id.str(); - for (int i = 0; i < GetSize(str) + 1; ++i) { - EXPECT_EQ(id[i], str.c_str()[i]); - } + TwinePool twines; + std::string id = twines.str(twines.add(NEW_ID)); + EXPECT_TRUE(id.starts_with("$auto")); + EXPECT_FALSE(id.starts_with("xyz")); + EXPECT_TRUE(id.starts_with("$auto$")); + EXPECT_FALSE(id.starts_with("abcdefghijklmn")); + EXPECT_TRUE(id.starts_with("$auto$rtlilTest")); + EXPECT_FALSE(id.starts_with("$auto$rtlilX")); } class WireRtlVsHdlIndexConversionTest : @@ -473,8 +463,9 @@ namespace RTLIL { ); TEST_P(WireRtlVsHdlIndexConversionTest, WireRtlVsHdlIndexConversion) { - std::unique_ptr mod = std::make_unique(); - Wire *wire = mod->addWire(ID(test), 10); + std::unique_ptr design = std::make_unique(); + Module *mod = design->addModule("$test_mod"); + Wire *wire = mod->addWire("\\test", 10); auto [upto, start_offset, width] = GetParam(); diff --git a/tests/unit/kernel/twineHashTest.cc b/tests/unit/kernel/twineHashTest.cc new file mode 100644 index 000000000..91dda0715 --- /dev/null +++ b/tests/unit/kernel/twineHashTest.cc @@ -0,0 +1,90 @@ +#include + +#include + +#include "kernel/rtlil.h" +#include "kernel/yosys.h" + +YOSYS_NAMESPACE_BEGIN + +namespace { + +// A name reached through a suffix chain must hash the same as the identical +// name interned as a single leaf, otherwise TwineSearch misses it. +TEST(TwineHashTest, Fragmentation) +{ + TwinePool pool; + DeepTwineHash hash{&pool}; + + IdString flat = pool.add(std::string("$abcdefghij")); + IdString base = pool.add(std::string("$abcde")); + IdString split = pool.add(Twine::Suffix{base, "fghij"}); + + EXPECT_EQ(hash(flat.untag()), hash(split.untag())); + EXPECT_EQ(hash(flat.untag()), hash(std::string_view("$abcdefghij"))); +} + +// The 8-byte buffering must not leak fragment boundaries at any offset. +TEST(TwineHashTest, SplitPoints) +{ + const std::string content = "$0123456789abcdefghijklmnopqr"; + + TwinePool pool; + DeepTwineHash hash{&pool}; + const size_t want = hash(std::string_view(content)); + + for (size_t cut = 1; cut < content.size(); cut++) { + IdString base = pool.add(content.substr(0, cut)); + IdString split = pool.add(Twine::Suffix{base, content.substr(cut)}); + EXPECT_EQ(hash(split.untag()), want) << "split after " << cut; + } +} + +TEST(TwineHashTest, DistinctContent) +{ + TwinePool twines; + DeepTwineHash hash{&twines}; + + std::set seen; + for (int i = 0; i < 4096; i++) { + IdString ref = twines.add(stringf("$name%d", i)); + seen.insert(hash(ref.untag())); + } + EXPECT_GT(seen.size(), 4000u); +} + +// Not an assertion of speed, but a harness: reports how long interning and +// rehashing a large pool takes so the cost of the hashing path is visible. +TEST(TwineHashTest, BenchmarkIntern) +{ + constexpr int kNames = 40000; + + TwinePool pool; + auto t0 = std::chrono::steady_clock::now(); + IdString prefix = pool.add(std::string("$bench")); + std::vector refs; + refs.reserve(kNames); + for (int i = 0; i < kNames; i++) + refs.push_back(pool.add(Twine::Suffix{prefix, stringf("$%d", i)})); + auto t1 = std::chrono::steady_clock::now(); + + TwineSearch search(&pool); + auto t2 = std::chrono::steady_clock::now(); + + for (int i = 0; i < kNames; i++) + ASSERT_EQ(search.find(pool.str(refs[i])), refs[i]) << "at " << i; + auto t3 = std::chrono::steady_clock::now(); + + auto ms = [](auto a, auto b) { + return std::chrono::duration_cast(b - a).count() / 1000.0; + }; + RecordProperty("intern_ms", std::to_string(ms(t0, t1))); + RecordProperty("search_build_ms", std::to_string(ms(t1, t2))); + RecordProperty("search_find_ms", std::to_string(ms(t2, t3))); + std::cerr << "[ BENCH ] intern " << ms(t0, t1) << " ms, TwineSearch build " + << ms(t1, t2) << " ms, " << kNames << " finds " << ms(t2, t3) << " ms\n"; +} + +} // namespace + +YOSYS_NAMESPACE_END diff --git a/tests/unit/kernel/twinePublicityTest.cc b/tests/unit/kernel/twinePublicityTest.cc new file mode 100644 index 000000000..7ca5d9991 --- /dev/null +++ b/tests/unit/kernel/twinePublicityTest.cc @@ -0,0 +1,167 @@ +#include + +#include "kernel/rtlil.h" +#include "kernel/yosys.h" + +YOSYS_NAMESPACE_BEGIN + +TEST(TwinePublicityTest, LeafEscape) +{ + TwinePool pool; + IdString pub = pool.add(std::string("\\foo")); + IdString priv = pool.add(std::string("$foo")); + + EXPECT_TRUE(pub.isPublic()); + EXPECT_FALSE(priv.isPublic()); + EXPECT_EQ(pool.str(pub), "\\foo"); + EXPECT_EQ(pool.unescaped_str(pub), "foo"); + EXPECT_EQ(pool.str(priv), "$foo"); + EXPECT_EQ(pool.unescaped_str(priv), "$foo"); +} + +TEST(TwinePublicityTest, EscapedDollar) +{ + // Verilog escaped identifier `\$foo` (public, content "$foo") must not + // collide with the private name `$foo` as a dict key. + TwinePool pool; + IdString pub = pool.add(std::string("\\$foo")); + IdString priv = pool.add(std::string("$foo")); + + EXPECT_EQ(pub.untag(), priv.untag()); // shared content node + EXPECT_NE(pub, priv); // distinct handles + EXPECT_EQ(pool.str(pub), "\\$foo"); + EXPECT_EQ(pool.str(priv), "$foo"); +} + +TEST(TwinePublicityTest, TagStability) +{ + TwinePool pool; + IdString a = pool.add(std::string("\\foo")); + IdString b = pool.add(std::string("\\foo")); + EXPECT_EQ(a, b); +} + +TEST(TwinePublicityTest, SuffixPublicity) +{ + TwinePool pool; + IdString pub = pool.add(std::string("\\base")); + IdString priv = pool.add(std::string("$base")); + + IdString pub_sfx = pool.add(Twine{Twine::Suffix{pub, "_1"}}); + IdString priv_sfx = pool.add(Twine{Twine::Suffix{priv, "_1"}}); + + EXPECT_TRUE(pub_sfx.isPublic()); + EXPECT_FALSE(priv_sfx.isPublic()); + EXPECT_EQ(pool.str(pub_sfx), "\\base_1"); + EXPECT_EQ(pool.str(priv_sfx), "$base_1"); +} + +TEST(TwinePublicityTest, StaticTags) +{ + TwinePool pool; + EXPECT_TRUE((ID::A).isPublic()); + EXPECT_EQ(pool.str(ID::A), "\\A"); + EXPECT_EQ(pool.unescaped_str(ID::A), "A"); + EXPECT_FALSE((ID($and)).isPublic()); + EXPECT_EQ(pool.str(ID($and)), "$and"); +} + +TEST(TwinePublicityTest, LookupTag) +{ + TwinePool pool; + IdString pub = pool.add(std::string("\\net")); + IdString priv = pool.add(std::string("$net")); + + TwineSearch search(&pool); + EXPECT_EQ(search.find("\\net"), pub); + EXPECT_EQ(search.find("$net"), priv); + EXPECT_EQ(search.find("\\A"), ID::A); + EXPECT_EQ(search.find("\\nonexistent"), IdString::Null); +} + +TEST(TwinePublicityTest, SearchUnifies) +{ + TwinePool pool; + IdString flat = pool.add(Twine::Leaf{"$abc"}); + IdString head = pool.add(Twine::Leaf{"$a"}); + IdString split = pool.add(Twine::Suffix{head, "bc"}); + + ASSERT_NE(flat, split); + ASSERT_EQ(pool.str(flat), pool.str(split)); + + TwineSearch search(&pool); + EXPECT_EQ(search.index.count(flat), 1u); + EXPECT_EQ(search.index.count(split), 1u); + EXPECT_EQ(search.index.count(head), 1u); + EXPECT_NE(search.find("$abc"), IdString::Null); + EXPECT_EQ(search.find("$a"), head); +} + +TEST(TwinePublicityTest, SearchPublicity) +{ + TwinePool pool; + IdString priv = pool.add(Twine::Leaf{"sig"}); + IdString pub = pool.add(std::string("\\sig")); + + ASSERT_EQ(priv, pub.untag()); + + TwineSearch search(&pool); + IdString found_pub = search.find("\\sig"); + IdString found_priv = search.find("sig"); + + EXPECT_TRUE(found_pub.isPublic()); + EXPECT_FALSE(found_priv.isPublic()); + EXPECT_EQ(found_pub.untag(), found_priv.untag()); + EXPECT_EQ(found_pub, pub); +} + +TEST(TwinePublicityTest, CopyTag) +{ + TwinePool src, dst; + IdString pub = src.add(std::string("\\xfer")); + IdString copied = dst.copy_from(src, pub); + EXPECT_TRUE(copied.isPublic()); + EXPECT_EQ(dst.str(copied), "\\xfer"); + // Static handles pass through tag and all. + EXPECT_EQ(dst.copy_from(src, ID::A), ID::A); +} + +TEST(TwinePublicityTest, GcRoots) +{ + TwinePool twines; + IdString pub = twines.add(std::string("\\keep")); + twines.add(std::string("\\drop")); + Yosys::pool roots{pub}; + EXPECT_EQ(twines.gc(roots), 1u); + EXPECT_EQ(twines.str(pub), "\\keep"); +} + +TEST(TwinePublicityTest, WireMasquerade) +{ + RTLIL::Design design; + RTLIL::Module *mod = design.addModule("\\top"); + + RTLIL::Wire *pub = mod->addWire("\\sig"); + RTLIL::Wire *priv = mod->addWire("$sig"); + + EXPECT_TRUE(pub->name.isPublic()); + EXPECT_FALSE(priv->name.isPublic()); + EXPECT_EQ(pub->name.escaped(), "\\sig"); + EXPECT_EQ(pub->name.unescape(), "sig"); + EXPECT_EQ(pub->name.str(), "\\sig"); + EXPECT_EQ(priv->name.escaped(), "$sig"); + EXPECT_EQ(priv->name.unescape(), "$sig"); + + // Distinct dict keys despite shared content. + EXPECT_NE(pub, priv); + TwineSearch search(&design.twines); + EXPECT_EQ(mod->wire(search.find("\\sig")), pub); + EXPECT_EQ(mod->wire(search.find("$sig")), priv); + + // uniquify keeps publicity. + IdString uniq = mod->uniquify(pub->name); + EXPECT_TRUE(uniq.isPublic()); + EXPECT_EQ(design.twines.str(uniq), "\\sig_1"); +} + +YOSYS_NAMESPACE_END diff --git a/tests/unit/kernel/twineSortTest.cc b/tests/unit/kernel/twineSortTest.cc new file mode 100644 index 000000000..7052d4c90 --- /dev/null +++ b/tests/unit/kernel/twineSortTest.cc @@ -0,0 +1,126 @@ +#include + +#include + +#include "kernel/rtlil.h" +#include "kernel/yosys.h" + +YOSYS_NAMESPACE_BEGIN + +namespace { + +std::vector bench_refs(TwinePool &twines, int count) +{ + IdString prefix = twines.add(std::string("$sortbench")); + std::vector refs; + refs.reserve(count); + for (int i = 0; i < count; i++) + refs.push_back(twines.add(Twine::Suffix{prefix, stringf("$%08d", (count - i) * 7919 % count)})); + return refs; +} + +TEST(TwineSortTest, RenderedOrder) +{ + TwinePool twines; + std::vector refs = { + twines.add(std::string("$c")), + twines.add(std::string("$a")), + twines.add(std::string("$b")), + }; + std::sort(refs.begin(), refs.end(), RTLIL::sort_by_id_str(twines)); + EXPECT_EQ(twines.str(refs[0]), "$a"); + EXPECT_EQ(twines.str(refs[1]), "$b"); + EXPECT_EQ(twines.str(refs[2]), "$c"); +} + +TEST(TwineSortTest, PublicOrder) +{ + TwinePool twines; + IdString pub = twines.add(std::string("\\same")); + IdString priv = pub.tag(false); + + ASSERT_EQ(twines.str(pub), "\\same"); + ASSERT_EQ(twines.str(priv), "same"); + + RTLIL::sort_by_id_str less(twines); + EXPECT_NE(less(pub, priv), less(priv, pub)); + EXPECT_TRUE(less(pub, priv)); +} + +TEST(TwineSortTest, WeakOrdering) +{ + TwinePool twines; + std::vector refs = bench_refs(twines, 500); + RTLIL::sort_by_id_str less(twines); + std::sort(refs.begin(), refs.end(), less); + for (size_t i = 1; i < refs.size(); i++) + ASSERT_FALSE(less(refs[i], refs[i - 1])) << "at " << i; +} + +TEST(TwineSortTest, GeneralWalk) +{ + TwinePool twines; + std::vector refs; + + IdString a = twines.add(std::string("$alpha")); + IdString b = twines.add(std::string("\\alpha")); + IdString c = twines.add(std::string("$alphabet")); + refs.insert(refs.end(), {a, b, c}); + + IdString deep = a; + for (int i = 0; i < 12; i++) { + deep = twines.add(Twine::Suffix{deep, stringf(".lvl%d", i)}); + refs.push_back(deep); + refs.push_back(deep.tag(true)); + } + + IdString other = twines.add(std::string("$alpha.")); + for (int i = 0; i < 12; i++) { + other = twines.add(Twine::Suffix{other, stringf("lvl%d.", i)}); + refs.push_back(other); + } + + RTLIL::sort_by_id_str less(twines); + for (IdString x : refs) + for (IdString y : refs) { + bool want = twines.str(x) < twines.str(y); + EXPECT_EQ(less(x, y), want) + << twines.str(x) << " vs " << twines.str(y); + } +} + +TEST(TwineSortTest, BenchmarkSort) +{ + constexpr int kNames = 50000; + + TwinePool twines; + std::vector refs = bench_refs(twines, kNames); + + auto t0 = std::chrono::steady_clock::now(); + std::sort(refs.begin(), refs.end(), RTLIL::sort_by_id_str(twines)); + auto t1 = std::chrono::steady_clock::now(); + + size_t flattens = 0; + std::vector naive = bench_refs(twines, kNames); + auto t2 = std::chrono::steady_clock::now(); + std::sort(naive.begin(), naive.end(), [&](IdString a, IdString b) { + flattens += 2; + return twines.str(a) < twines.str(b); + }); + auto t3 = std::chrono::steady_clock::now(); + + auto ms = [](auto a, auto b) { + return std::chrono::duration_cast(b - a).count() / 1000.0; + }; + std::cerr << "[ BENCH ] " << kNames << " refs: walked " << ms(t0, t1) + << " ms vs per-comparison " << ms(t2, t3) << " ms (" + << flattens << " flattens vs " << kNames << ")\n"; + RecordProperty("walked_ms", std::to_string(ms(t0, t1))); + RecordProperty("naive_ms", std::to_string(ms(t2, t3))); + + EXPECT_LT(ms(t0, t1), ms(t2, t3)); +} + +} // namespace + +YOSYS_NAMESPACE_END diff --git a/tests/unit/yosysSetupEnv.cc b/tests/unit/yosysSetupEnv.cc new file mode 100644 index 000000000..3f6ad2349 --- /dev/null +++ b/tests/unit/yosysSetupEnv.cc @@ -0,0 +1,11 @@ +#include + +#include "kernel/yosys.h" + +namespace { + struct YosysSetupEnvironment : ::testing::Environment { + void SetUp() override { Yosys::yosys_setup(); } + }; + const ::testing::Environment *yosys_setup_env = + ::testing::AddGlobalTestEnvironment(new YosysSetupEnvironment); +} diff --git a/tests/various/design_equal_pools.ys b/tests/various/design_equal_pools.ys new file mode 100644 index 000000000..39de62b1c --- /dev/null +++ b/tests/various/design_equal_pools.ys @@ -0,0 +1,50 @@ +read_rtlil <