tests: cover twines, name publicity, hashing and sorting

This commit is contained in:
Emil J. Tywoniak
2026-08-13 18:15:58 +02:00
parent 5088e8f6ed
commit f7d1a491d7
15 changed files with 1105 additions and 30 deletions
+23
View File
@@ -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 <<EOF
wire input 1 \\home.repo.chain.in
wire output 2 \\home.repo.chain.out
EOF
diff temp/suffix-chain-expected.names temp/suffix-chain-gc-resolved.names
+13
View File
@@ -0,0 +1,13 @@
autoidx 1
twines
leaf 1053 "home"
suffix 1054 1053 ".repo"
suffix 1055 1054 ".chain"
suffix 1056 1055 ".in"
suffix 1057 1055 ".out"
end
module \chain
wire input 1 $pub@1056 # \home.repo.chain.in
wire output 2 $pub@1057 # \home.repo.chain.out
connect $pub@1057 $pub@1056 # \home.repo.chain.out \home.repo.chain.in
end
+12
View File
@@ -0,0 +1,12 @@
autoidx 1
twines
leaf 1053 "everything"
suffix 1054 1053 ".a"
suffix 1055 1053 ".b"
leaf 1056 "tiny"
end
module $pub@1056 # \tiny
wire input 1 $pub@1054 # \everything.a
wire output 2 $pub@1055 # \everything.b
connect $pub@1055 $pub@1054 # \everything.b \everything.a
end
+38
View File
@@ -0,0 +1,38 @@
set -euo pipefail
mkdir -p temp
# If a different version of Yosys with different constids.inc wrote a file,
# the integers have to get remapped when read into this version
cat > temp/moved-static.il <<EOF
autoidx 1
twines
leaf 42 "A"
suffix 999999 42 ".sub"
end
module \$pub@999999
wire input 1 \\a
end
EOF
${YOSYS} -p "read_rtlil temp/moved-static.il; write_rtlil temp/moved-static-out.il"
grep -q '# \\A.sub' temp/moved-static-out.il
cat > temp/stale-dynamic.il <<EOF
autoidx 1
twines
leaf 3 "everything"
suffix 4 3 ".sub"
leaf 5 "tiny"
end
module \$pub@5
wire input 1 \$pub@4
end
EOF
${YOSYS} -p "read_rtlil temp/stale-dynamic.il; write_rtlil temp/stale-dynamic-out.il"
grep -q '# \\tiny' temp/stale-dynamic-out.il
grep -q '# \\everything.sub' temp/stale-dynamic-out.il
${YOSYS} -p "read_rtlil temp/moved-static.il; write_rtlil temp/roundtrip.il"
grep -q 'leaf .* "A"' temp/roundtrip.il
+1
View File
@@ -6,6 +6,7 @@ yosys_gtest(kernel
ioTest.cc
logTest.cc
modindexTest.cc
nameMasqTest.cc
pooledNameTest.cc
rtlilHelpers.h
rtlilStringTest.cc
+184
View File
@@ -0,0 +1,184 @@
#include <gtest/gtest.h>
#include <chrono>
#include <cstdio>
#include <cstdlib>
#include <string>
#include <sys/resource.h>
#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<double, std::milli>(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
+263
View File
@@ -0,0 +1,263 @@
#include <gtest/gtest.h>
#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<decltype(left.first), IdString>));
EXPECT_TRUE((std::is_same_v<decltype(right.second), IdString>));
EXPECT_TRUE((std::is_same_v<decltype(both.first), IdString>));
EXPECT_TRUE((std::is_same_v<decltype(both.second), IdString>));
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
+84
View File
@@ -0,0 +1,84 @@
#include <gtest/gtest.h>
#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<PooledName, int> 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
+21 -30
View File
@@ -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<Module> mod = std::make_unique<Module>();
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> design = std::make_unique<Design>();
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<Module> mod = std::make_unique<Module>();
Wire *wire = mod->addWire(ID(test), 10);
std::unique_ptr<Design> design = std::make_unique<Design>();
Module *mod = design->addModule("$test_mod");
Wire *wire = mod->addWire("\\test", 10);
auto [upto, start_offset, width] = GetParam();
+90
View File
@@ -0,0 +1,90 @@
#include <gtest/gtest.h>
#include <chrono>
#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<size_t> 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<IdString> 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<std::chrono::microseconds>(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
+167
View File
@@ -0,0 +1,167 @@
#include <gtest/gtest.h>
#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<IdString> 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
+126
View File
@@ -0,0 +1,126 @@
#include <gtest/gtest.h>
#include <chrono>
#include "kernel/rtlil.h"
#include "kernel/yosys.h"
YOSYS_NAMESPACE_BEGIN
namespace {
std::vector<IdString> bench_refs(TwinePool &twines, int count)
{
IdString prefix = twines.add(std::string("$sortbench"));
std::vector<IdString> 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<IdString> 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<IdString> 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<IdString> 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<IdString> 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<IdString> 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<std::chrono::microseconds>(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
+11
View File
@@ -0,0 +1,11 @@
#include <gtest/gtest.h>
#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);
}
+50
View File
@@ -0,0 +1,50 @@
read_rtlil <<EOT
module \zz_top_mod
wire width 1 input 1 \zz_in_alpha
wire width 1 input 2 \zz_in_beta
wire width 1 output 3 \zz_out_gamma
cell $and \zz_the_gate
parameter \A_SIGNED 0
parameter \B_SIGNED 0
parameter \A_WIDTH 1
parameter \B_WIDTH 1
parameter \Y_WIDTH 1
connect \A \zz_in_alpha
connect \B \zz_in_beta
connect \Y \zz_out_gamma
end
end
EOT
design -save golden
design -reset
read_rtlil <<EOT
module \zz_filler
wire width 1 \zz_pad_one
wire width 1 \zz_pad_two
wire width 1 \zz_pad_three
wire width 1 \zz_pad_four
end
EOT
delete zz_filler
read_rtlil <<EOT
module \zz_top_mod
wire width 1 input 1 \zz_in_alpha
wire width 1 input 2 \zz_in_beta
wire width 1 output 3 \zz_out_gamma
cell $and \zz_the_gate
parameter \A_SIGNED 0
parameter \B_SIGNED 0
parameter \A_WIDTH 1
parameter \B_WIDTH 1
parameter \Y_WIDTH 1
connect \A \zz_in_alpha
connect \B \zz_in_beta
connect \Y \zz_out_gamma
end
end
EOT
design_equal golden
+22
View File
@@ -0,0 +1,22 @@
logger -expect error "Module top missing wire other in second design" 1
read_rtlil <<EOT
module \top
wire width 1 input 1 \a
wire width 1 output 2 \renamed
connect \renamed \a
end
EOT
design -save golden
design -reset
read_rtlil <<EOT
module \top
wire width 1 input 1 \a
wire width 1 output 2 \other
connect \other \a
end
EOT
design_equal golden