mirror of
https://github.com/YosysHQ/yosys
synced 2026-07-19 21:55:48 +00:00
Merge remote-tracking branch 'upstream/main' into silimate
This commit is contained in:
commit
e58125b605
834 changed files with 25281 additions and 8780 deletions
29
tests/unit/CMakeLists.txt
Normal file
29
tests/unit/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
include(CTest)
|
||||
include(GoogleTest)
|
||||
|
||||
function(yosys_gtest arg_TARGET)
|
||||
cmake_parse_arguments(PARSE_ARGV 1 arg "" "" "COMPONENTS")
|
||||
set(arg_SOURCES ${arg_UNPARSED_ARGUMENTS})
|
||||
|
||||
set(target "gtest-${arg_TARGET}")
|
||||
add_executable(${target} ${arg_SOURCES})
|
||||
target_link_libraries(${target} PRIVATE
|
||||
GTest::gmock_main
|
||||
yosys_common
|
||||
$<${YOSYS_ENABLE_PYTHON}:Python3::Python>
|
||||
)
|
||||
yosys_expand_components(test_components essentials ${arg_COMPONENTS})
|
||||
yosys_link_components(${target} PRIVATE ${test_components})
|
||||
|
||||
if(NOT CMAKE_CROSSCOMPILING)
|
||||
gtest_discover_tests(${target})
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
if (GTest_FOUND)
|
||||
add_subdirectory(kernel)
|
||||
add_subdirectory(opt)
|
||||
add_subdirectory(techmap)
|
||||
|
||||
enable_testing()
|
||||
endif()
|
||||
|
|
@ -4,10 +4,10 @@ UNAME_S := $(shell uname -s)
|
|||
GTEST_PREFIX := $(shell brew --prefix googletest 2>/dev/null)
|
||||
ifeq ($(GTEST_PREFIX),)
|
||||
GTEST_CXXFLAGS :=
|
||||
GTEST_LDFLAGS := -lgtest -lgtest_main
|
||||
GTEST_LDFLAGS := -lgtest -lgmock -lgtest_main
|
||||
else
|
||||
GTEST_CXXFLAGS := -I$(GTEST_PREFIX)/include
|
||||
GTEST_LDFLAGS := -L$(GTEST_PREFIX)/lib -lgtest -lgtest_main
|
||||
GTEST_LDFLAGS := -L$(GTEST_PREFIX)/lib -lgtest -lgmock -lgtest_main
|
||||
endif
|
||||
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
|
|
|
|||
14
tests/unit/kernel/CMakeLists.txt
Normal file
14
tests/unit/kernel/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
yosys_gtest(kernel
|
||||
bitpatternTest.cc
|
||||
cellTypesTest.cc
|
||||
hashTest.cc
|
||||
ioTest.cc
|
||||
logTest.cc
|
||||
modindexTest.cc
|
||||
rtlilHelpers.h
|
||||
rtlilStringTest.cc
|
||||
rtlilTest.cc
|
||||
sigspecExtractTest.cc
|
||||
sigspecRemove2Test.cc
|
||||
threadingTest.cc
|
||||
)
|
||||
87
tests/unit/kernel/cellTypesTest.cc
Normal file
87
tests/unit/kernel/cellTypesTest.cc
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
#include <gtest/gtest.h>
|
||||
#include "kernel/yosys.h"
|
||||
#include "kernel/yosys_common.h"
|
||||
#include "kernel/celltypes.h"
|
||||
#include "kernel/newcelltypes.h"
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
YOSYS_NAMESPACE_BEGIN
|
||||
|
||||
TEST(CellTypesTest, basic)
|
||||
{
|
||||
yosys_setup();
|
||||
log_files.push_back(stdout);
|
||||
CellTypes older;
|
||||
NewCellTypes newer;
|
||||
older.setup(nullptr);
|
||||
newer.setup(nullptr);
|
||||
older.setup_type(ID(bleh), {ID::G}, {ID::H, ID::I}, false, true);
|
||||
newer.setup_type(ID(bleh), {ID::G}, {ID::H, ID::I}, false, true);
|
||||
EXPECT_EQ(older.cell_known(ID(aaaaa)), newer.cell_known(ID(aaaaa)));
|
||||
EXPECT_EQ(older.cell_known(ID($and)), newer.cell_known(ID($and)));
|
||||
auto check_port = [&](auto type, auto port) {
|
||||
EXPECT_EQ(older.cell_port_dir(type, port), newer.cell_port_dir(type, port));
|
||||
EXPECT_EQ(older.cell_input(type, port), newer.cell_input(type, port));
|
||||
EXPECT_EQ(older.cell_output(type, port), newer.cell_output(type, port));
|
||||
};
|
||||
|
||||
// ground truth
|
||||
const pool<IdString> expected_ff_types = {
|
||||
ID($sr), ID($ff), ID($dff), ID($dffe), ID($dffsr), ID($dffsre),
|
||||
ID($adff), ID($adffe), ID($aldff), ID($aldffe),
|
||||
ID($sdff), ID($sdffe), ID($sdffce),
|
||||
ID($dlatch), ID($adlatch), ID($dlatchsr),
|
||||
ID($_DFFE_NN_), ID($_DFFE_NP_), ID($_DFFE_PN_), ID($_DFFE_PP_),
|
||||
ID($_DFFSR_NNN_), ID($_DFFSR_NNP_), ID($_DFFSR_NPN_), ID($_DFFSR_NPP_),
|
||||
ID($_DFFSR_PNN_), ID($_DFFSR_PNP_), ID($_DFFSR_PPN_), ID($_DFFSR_PPP_),
|
||||
ID($_DFFSRE_NNNN_), ID($_DFFSRE_NNNP_), ID($_DFFSRE_NNPN_), ID($_DFFSRE_NNPP_),
|
||||
ID($_DFFSRE_NPNN_), ID($_DFFSRE_NPNP_), ID($_DFFSRE_NPPN_), ID($_DFFSRE_NPPP_),
|
||||
ID($_DFFSRE_PNNN_), ID($_DFFSRE_PNNP_), ID($_DFFSRE_PNPN_), ID($_DFFSRE_PNPP_),
|
||||
ID($_DFFSRE_PPNN_), ID($_DFFSRE_PPNP_), ID($_DFFSRE_PPPN_), ID($_DFFSRE_PPPP_),
|
||||
ID($_DFF_N_), ID($_DFF_P_),
|
||||
ID($_DFF_NN0_), ID($_DFF_NN1_), ID($_DFF_NP0_), ID($_DFF_NP1_),
|
||||
ID($_DFF_PN0_), ID($_DFF_PN1_), ID($_DFF_PP0_), ID($_DFF_PP1_),
|
||||
ID($_DFFE_NN0N_), ID($_DFFE_NN0P_), ID($_DFFE_NN1N_), ID($_DFFE_NN1P_),
|
||||
ID($_DFFE_NP0N_), ID($_DFFE_NP0P_), ID($_DFFE_NP1N_), ID($_DFFE_NP1P_),
|
||||
ID($_DFFE_PN0N_), ID($_DFFE_PN0P_), ID($_DFFE_PN1N_), ID($_DFFE_PN1P_),
|
||||
ID($_DFFE_PP0N_), ID($_DFFE_PP0P_), ID($_DFFE_PP1N_), ID($_DFFE_PP1P_),
|
||||
ID($_ALDFF_NN_), ID($_ALDFF_NP_), ID($_ALDFF_PN_), ID($_ALDFF_PP_),
|
||||
ID($_ALDFFE_NNN_), ID($_ALDFFE_NNP_), ID($_ALDFFE_NPN_), ID($_ALDFFE_NPP_),
|
||||
ID($_ALDFFE_PNN_), ID($_ALDFFE_PNP_), ID($_ALDFFE_PPN_), ID($_ALDFFE_PPP_),
|
||||
ID($_SDFF_NN0_), ID($_SDFF_NN1_), ID($_SDFF_NP0_), ID($_SDFF_NP1_),
|
||||
ID($_SDFF_PN0_), ID($_SDFF_PN1_), ID($_SDFF_PP0_), ID($_SDFF_PP1_),
|
||||
ID($_SDFFE_NN0N_), ID($_SDFFE_NN0P_), ID($_SDFFE_NN1N_), ID($_SDFFE_NN1P_),
|
||||
ID($_SDFFE_NP0N_), ID($_SDFFE_NP0P_), ID($_SDFFE_NP1N_), ID($_SDFFE_NP1P_),
|
||||
ID($_SDFFE_PN0N_), ID($_SDFFE_PN0P_), ID($_SDFFE_PN1N_), ID($_SDFFE_PN1P_),
|
||||
ID($_SDFFE_PP0N_), ID($_SDFFE_PP0P_), ID($_SDFFE_PP1N_), ID($_SDFFE_PP1P_),
|
||||
ID($_SDFFCE_NN0N_), ID($_SDFFCE_NN0P_), ID($_SDFFCE_NN1N_), ID($_SDFFCE_NN1P_),
|
||||
ID($_SDFFCE_NP0N_), ID($_SDFFCE_NP0P_), ID($_SDFFCE_NP1N_), ID($_SDFFCE_NP1P_),
|
||||
ID($_SDFFCE_PN0N_), ID($_SDFFCE_PN0P_), ID($_SDFFCE_PN1N_), ID($_SDFFCE_PN1P_),
|
||||
ID($_SDFFCE_PP0N_), ID($_SDFFCE_PP0P_), ID($_SDFFCE_PP1N_), ID($_SDFFCE_PP1P_),
|
||||
ID($_SR_NN_), ID($_SR_NP_), ID($_SR_PN_), ID($_SR_PP_),
|
||||
ID($_DLATCH_N_), ID($_DLATCH_P_),
|
||||
ID($_DLATCH_NN0_), ID($_DLATCH_NN1_), ID($_DLATCH_NP0_), ID($_DLATCH_NP1_),
|
||||
ID($_DLATCH_PN0_), ID($_DLATCH_PN1_), ID($_DLATCH_PP0_), ID($_DLATCH_PP1_),
|
||||
ID($_DLATCHSR_NNN_), ID($_DLATCHSR_NNP_), ID($_DLATCHSR_NPN_), ID($_DLATCHSR_NPP_),
|
||||
ID($_DLATCHSR_PNN_), ID($_DLATCHSR_PNP_), ID($_DLATCHSR_PPN_), ID($_DLATCHSR_PPP_),
|
||||
ID($_FF_),
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < static_cast<size_t>(RTLIL::StaticId::STATIC_ID_END); i++) {
|
||||
IdString type;
|
||||
type.index_ = i;
|
||||
EXPECT_EQ(older.cell_known(type), newer.cell_known(type));
|
||||
if (older.cell_evaluable(type) != newer.cell_evaluable(type))
|
||||
std::cout << type.str() << "\n";
|
||||
EXPECT_EQ(older.cell_evaluable(type), newer.cell_evaluable(type));
|
||||
for (auto port : StaticCellTypes::builder.cells.data()->inputs.ports)
|
||||
check_port(type, port);
|
||||
for (auto port : StaticCellTypes::builder.cells.data()->outputs.ports)
|
||||
check_port(type, port);
|
||||
EXPECT_EQ(expected_ff_types.count(type) > 0, StaticCellTypes::categories.is_ff(type));
|
||||
}
|
||||
yosys_shutdown();
|
||||
}
|
||||
|
||||
YOSYS_NAMESPACE_END
|
||||
47
tests/unit/kernel/modindexTest.cc
Normal file
47
tests/unit/kernel/modindexTest.cc
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include "kernel/modtools.h"
|
||||
#include "kernel/rtlil.h"
|
||||
|
||||
YOSYS_NAMESPACE_BEGIN
|
||||
|
||||
TEST(ModIndexSwapTest, has)
|
||||
{
|
||||
Design* d = new Design;
|
||||
Module* m = d->addModule("$m");
|
||||
Wire* o = m->addWire("$o", 2);
|
||||
o->port_input = true;
|
||||
Wire* i = m->addWire("$i", 2);
|
||||
i->port_input = true;
|
||||
m->fixup_ports();
|
||||
m->addNot("$not", i, o);
|
||||
auto mi = ModIndex(m);
|
||||
mi.reload_module();
|
||||
for (auto [sb, info] : mi.database) {
|
||||
EXPECT_TRUE(mi.database.find(sb) != mi.database.end());
|
||||
}
|
||||
m->swap_names(i, o);
|
||||
for (auto [sb, info] : mi.database) {
|
||||
EXPECT_TRUE(mi.database.find(sb) != mi.database.end());
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ModIndexDeleteTest, has)
|
||||
{
|
||||
if (log_files.empty()) log_files.emplace_back(stdout);
|
||||
Design* d = new Design;
|
||||
Module* m = d->addModule("$m");
|
||||
Wire* w = m->addWire("$w");
|
||||
Wire* o = m->addWire("$o");
|
||||
o->port_output = true;
|
||||
m->fixup_ports();
|
||||
Cell* not_ = m->addNotGate("$not", w, o);
|
||||
auto mi = ModIndex(m);
|
||||
mi.reload_module();
|
||||
mi.dump_db();
|
||||
Wire* a = m->addWire("\\a");
|
||||
not_->setPort(ID::A, a);
|
||||
EXPECT_TRUE(mi.ok());
|
||||
}
|
||||
|
||||
YOSYS_NAMESPACE_END
|
||||
71
tests/unit/kernel/rtlilHelpers.h
Normal file
71
tests/unit/kernel/rtlilHelpers.h
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
#ifndef RTLIL_HELPERS_H
|
||||
#define RTLIL_HELPERS_H
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "kernel/rtlil.h"
|
||||
|
||||
YOSYS_NAMESPACE_BEGIN
|
||||
|
||||
class SigSpecRepTest : public ::testing::Test {
|
||||
protected:
|
||||
Design* d;
|
||||
Module* m;
|
||||
|
||||
void SetUp() override {
|
||||
d = new Design;
|
||||
m = d->addModule("$test");
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
delete d;
|
||||
}
|
||||
|
||||
// Create n wires with given width
|
||||
std::vector<Wire*> createWires(int count, int width = 4) {
|
||||
std::vector<Wire*> wires;
|
||||
for (int i = 0; i < count; i++) {
|
||||
Wire* w = m->addWire(stringf("$w%d", i), width);
|
||||
wires.push_back(w);
|
||||
}
|
||||
return wires;
|
||||
}
|
||||
|
||||
// Append all wires to a SigSpec
|
||||
SigSpec wiresAsSigSpec(const std::vector<Wire*>& wires) {
|
||||
SigSpec sig;
|
||||
for (auto w : wires)
|
||||
sig.append(w);
|
||||
return sig;
|
||||
}
|
||||
|
||||
// Create a SigSpec of constants
|
||||
SigSpec constsAsSigSpec(int count, int width = 4) {
|
||||
SigSpec sig;
|
||||
for (int i = 0; i < count; i++)
|
||||
sig.append(Const(i, width));
|
||||
return sig;
|
||||
}
|
||||
|
||||
// Convert wires to pool of SigBits
|
||||
pool<SigBit> wiresToPool(const std::vector<Wire*>& wires) {
|
||||
pool<SigBit> pool;
|
||||
for (auto w : wires)
|
||||
for (auto &bit : SigSpec(w))
|
||||
pool.insert(bit);
|
||||
return pool;
|
||||
}
|
||||
|
||||
// Convert wires to set of SigBits
|
||||
std::set<SigBit> wiresToSet(const std::vector<Wire*>& wires) {
|
||||
std::set<SigBit> set;
|
||||
for (auto w : wires)
|
||||
for (auto &bit : SigSpec(w))
|
||||
set.insert(bit);
|
||||
return set;
|
||||
}
|
||||
};
|
||||
|
||||
YOSYS_NAMESPACE_END
|
||||
|
||||
#endif /* RTLIL_HELPERS_H */
|
||||
89
tests/unit/kernel/sigspecExtractTest.cc
Normal file
89
tests/unit/kernel/sigspecExtractTest.cc
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include "kernel/rtlil.h"
|
||||
#include "tests/unit/kernel/rtlilHelpers.h"
|
||||
|
||||
YOSYS_NAMESPACE_BEGIN
|
||||
|
||||
namespace RTLIL {
|
||||
TEST_F(SigSpecRepTest, Extract)
|
||||
{
|
||||
{
|
||||
std::vector<Wire*> wires;
|
||||
SigSpec sig;
|
||||
for (int i = 0; i < 4; i++)
|
||||
wires.push_back(m->addWire(stringf("$w%d", i), 4));
|
||||
for (auto w : wires)
|
||||
sig.append(w);
|
||||
|
||||
SigSpec extractedFirst = sig.extract(0, 4);
|
||||
SigSpec extractedSecond = sig.extract(4, 4);
|
||||
SigSpec extractedLast = sig.extract(12, 4);
|
||||
EXPECT_EQ(extractedFirst.rep_, SigSpec::Representation::CHUNK);
|
||||
EXPECT_EQ(extractedSecond.rep_, SigSpec::Representation::CHUNK);
|
||||
EXPECT_EQ(extractedLast.rep_, SigSpec::Representation::CHUNK);
|
||||
EXPECT_EQ(extractedFirst.as_wire(), wires[0]);
|
||||
EXPECT_EQ(extractedSecond.as_wire(), wires[1]);
|
||||
EXPECT_EQ(extractedLast.as_wire(), wires[3]);
|
||||
}
|
||||
|
||||
{
|
||||
std::vector<SigSpec> consts;
|
||||
SigSpec sig;
|
||||
for (int i = 0; i < 4; i++)
|
||||
consts.push_back(Const(i, 4));
|
||||
for (auto c : consts)
|
||||
sig.append(c);
|
||||
|
||||
SigSpec extractedFirst = sig.extract(0, 4);
|
||||
SigSpec extractedSecond = sig.extract(4, 4);
|
||||
SigSpec extractedLast = sig.extract(12, 4);
|
||||
EXPECT_EQ(extractedFirst.rep_, SigSpec::Representation::CHUNK);
|
||||
EXPECT_EQ(extractedSecond.rep_, SigSpec::Representation::CHUNK);
|
||||
EXPECT_EQ(extractedLast.rep_, SigSpec::Representation::CHUNK);
|
||||
EXPECT_EQ(extractedFirst, consts[0]);
|
||||
EXPECT_EQ(extractedSecond, consts[1]);
|
||||
EXPECT_EQ(extractedLast, consts[3]);
|
||||
}
|
||||
|
||||
{
|
||||
SigSpec sig;
|
||||
sig.append(Const(0, 4));
|
||||
Wire* w = m->addWire("$foo", 4);
|
||||
sig.append(w);
|
||||
sig.append(Const(15, 4));
|
||||
|
||||
SigSpec extractedFirst = sig.extract(0, 4);
|
||||
SigSpec extractedSecond = sig.extract(4, 4);
|
||||
SigSpec extractedLast = sig.extract(8, 4);
|
||||
EXPECT_EQ(extractedFirst.rep_, SigSpec::Representation::CHUNK);
|
||||
EXPECT_EQ(extractedSecond.rep_, SigSpec::Representation::CHUNK);
|
||||
EXPECT_EQ(extractedLast.rep_, SigSpec::Representation::CHUNK);
|
||||
|
||||
SigSpec extractedFirstTwo = sig.extract(0, 8);
|
||||
EXPECT_EQ(extractedFirstTwo.rep_, SigSpec::Representation::BITS);
|
||||
SigSpec extractedLastTwo = sig.extract(4, 8);
|
||||
EXPECT_EQ(extractedLastTwo.rep_, SigSpec::Representation::BITS);
|
||||
|
||||
EXPECT_EQ(extractedFirstTwo[0], State::S0);
|
||||
EXPECT_EQ(extractedFirstTwo[1], State::S0);
|
||||
EXPECT_EQ(extractedFirstTwo[2], State::S0);
|
||||
EXPECT_EQ(extractedFirstTwo[3], State::S0);
|
||||
EXPECT_EQ(extractedFirstTwo[4], SigBit(w, 0));
|
||||
EXPECT_EQ(extractedFirstTwo[5], SigBit(w, 1));
|
||||
EXPECT_EQ(extractedFirstTwo[6], SigBit(w, 2));
|
||||
EXPECT_EQ(extractedFirstTwo[7], SigBit(w, 3));
|
||||
|
||||
EXPECT_EQ(extractedLastTwo[0], SigBit(w, 0));
|
||||
EXPECT_EQ(extractedLastTwo[1], SigBit(w, 1));
|
||||
EXPECT_EQ(extractedLastTwo[2], SigBit(w, 2));
|
||||
EXPECT_EQ(extractedLastTwo[3], SigBit(w, 3));
|
||||
EXPECT_EQ(extractedLastTwo[4], State::S1);
|
||||
EXPECT_EQ(extractedLastTwo[5], State::S1);
|
||||
EXPECT_EQ(extractedLastTwo[6], State::S1);
|
||||
EXPECT_EQ(extractedLastTwo[7], State::S1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
YOSYS_NAMESPACE_END
|
||||
333
tests/unit/kernel/sigspecRemove2Test.cc
Normal file
333
tests/unit/kernel/sigspecRemove2Test.cc
Normal file
|
|
@ -0,0 +1,333 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include "kernel/rtlil.h"
|
||||
|
||||
YOSYS_NAMESPACE_BEGIN
|
||||
|
||||
// Test fixture with helper functions
|
||||
class SigSpecRepTest : public ::testing::Test {
|
||||
protected:
|
||||
Design* d;
|
||||
Module* m;
|
||||
|
||||
void SetUp() override {
|
||||
d = new Design;
|
||||
m = d->addModule("$test");
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
delete d;
|
||||
}
|
||||
|
||||
// Create n wires with given width
|
||||
std::vector<Wire*> createWires(int count, int width = 4) {
|
||||
std::vector<Wire*> wires;
|
||||
for (int i = 0; i < count; i++) {
|
||||
Wire* w = m->addWire(stringf("$w%d", i), width);
|
||||
wires.push_back(w);
|
||||
}
|
||||
return wires;
|
||||
}
|
||||
|
||||
// Append all wires to a SigSpec
|
||||
SigSpec wiresAsSigSpec(const std::vector<Wire*>& wires) {
|
||||
SigSpec sig;
|
||||
for (auto w : wires)
|
||||
sig.append(w);
|
||||
return sig;
|
||||
}
|
||||
|
||||
// Create a SigSpec of constants
|
||||
SigSpec constsAsSigSpec(int count, int width = 4) {
|
||||
SigSpec sig;
|
||||
for (int i = 0; i < count; i++)
|
||||
sig.append(Const(i, width));
|
||||
return sig;
|
||||
}
|
||||
|
||||
// Convert wires to pool of SigBits
|
||||
pool<SigBit> wiresToPool(const std::vector<Wire*>& wires) {
|
||||
pool<SigBit> pool;
|
||||
for (auto w : wires)
|
||||
for (auto &bit : SigSpec(w))
|
||||
pool.insert(bit);
|
||||
return pool;
|
||||
}
|
||||
|
||||
// Convert wires to set of SigBits
|
||||
std::set<SigBit> wiresToSet(const std::vector<Wire*>& wires) {
|
||||
std::set<SigBit> set;
|
||||
for (auto w : wires)
|
||||
for (auto &bit : SigSpec(w))
|
||||
set.insert(bit);
|
||||
return set;
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(SigSpecRepTest, WithSigSpecPattern)
|
||||
{
|
||||
auto wires = createWires(4);
|
||||
SigSpec sig = wiresAsSigSpec(wires);
|
||||
SigSpec pattern(wires[1]); // Remove w2
|
||||
SigSpec other = constsAsSigSpec(4);
|
||||
|
||||
EXPECT_EQ(sig.size(), 16);
|
||||
sig.remove2(pattern, &other);
|
||||
EXPECT_EQ(sig.size(), 12);
|
||||
EXPECT_EQ(other.size(), 12);
|
||||
|
||||
// Verify correct wires remain (w1, w3, w4)
|
||||
SigSpec expected;
|
||||
expected.append(wires[0]);
|
||||
expected.append(wires[2]);
|
||||
expected.append(wires[3]);
|
||||
EXPECT_EQ(sig, expected);
|
||||
}
|
||||
|
||||
TEST_F(SigSpecRepTest, WithPoolPattern)
|
||||
{
|
||||
auto wires = createWires(3);
|
||||
SigSpec sig = wiresAsSigSpec(wires);
|
||||
auto pattern = wiresToPool({wires[1]});
|
||||
SigSpec other = constsAsSigSpec(3);
|
||||
|
||||
EXPECT_EQ(sig.size(), 12);
|
||||
sig.remove2(pattern, &other);
|
||||
EXPECT_EQ(sig.size(), 8);
|
||||
EXPECT_EQ(other.size(), 8);
|
||||
|
||||
// Verify correct wires remain (w0, w2)
|
||||
SigSpec expected;
|
||||
expected.append(wires[0]);
|
||||
expected.append(wires[2]);
|
||||
EXPECT_EQ(sig, expected);
|
||||
}
|
||||
|
||||
TEST_F(SigSpecRepTest, WithSetPattern)
|
||||
{
|
||||
auto wires = createWires(3);
|
||||
SigSpec sig = wiresAsSigSpec(wires);
|
||||
auto pattern = wiresToSet({wires[1]});
|
||||
SigSpec other = constsAsSigSpec(3);
|
||||
|
||||
EXPECT_EQ(sig.size(), 12);
|
||||
sig.remove2(pattern, &other);
|
||||
EXPECT_EQ(sig.size(), 8);
|
||||
EXPECT_EQ(other.size(), 8);
|
||||
|
||||
// Verify correct wires remain (w0, w2)
|
||||
SigSpec expected;
|
||||
expected.append(wires[0]);
|
||||
expected.append(wires[2]);
|
||||
EXPECT_EQ(sig, expected);
|
||||
}
|
||||
|
||||
TEST_F(SigSpecRepTest, ManyElements)
|
||||
{
|
||||
const int num_wires = 100;
|
||||
auto wires = createWires(num_wires);
|
||||
SigSpec sig = wiresAsSigSpec(wires);
|
||||
|
||||
// Remove every other wire
|
||||
std::vector<Wire*> to_remove;
|
||||
for (int i = 0; i < num_wires; i += 2)
|
||||
to_remove.push_back(wires[i]);
|
||||
auto pattern = wiresToPool(to_remove);
|
||||
|
||||
EXPECT_EQ(sig.size(), num_wires * 4);
|
||||
sig.remove2(pattern, nullptr);
|
||||
EXPECT_EQ(sig.size(), (num_wires / 2) * 4);
|
||||
|
||||
// Verify odd-indexed wires remain (w1, w3, w5, ..., w99)
|
||||
SigSpec expected;
|
||||
for (int i = 1; i < num_wires; i += 2)
|
||||
expected.append(wires[i]);
|
||||
EXPECT_EQ(sig, expected);
|
||||
}
|
||||
|
||||
// Test remove2 with very large dataset to check scaling
|
||||
TEST_F(SigSpecRepTest, VeryLargeScalingTest)
|
||||
{
|
||||
const int num_wires = 50000;
|
||||
auto wires = createWires(num_wires);
|
||||
SigSpec sig = wiresAsSigSpec(wires);
|
||||
SigSpec other = constsAsSigSpec(num_wires);
|
||||
|
||||
// Create pattern with many chunks (one per wire)
|
||||
SigSpec pattern;
|
||||
for (int i = 0; i < num_wires; i += 2)
|
||||
pattern.append(wires[i]);
|
||||
|
||||
EXPECT_EQ(sig.size(), num_wires * 4);
|
||||
sig.remove2(pattern, &other);
|
||||
EXPECT_EQ(sig.size(), (num_wires / 2) * 4);
|
||||
EXPECT_EQ(other.size(), (num_wires / 2) * 4);
|
||||
|
||||
// Spot-check: odd-indexed wires should remain at expected positions
|
||||
for (int i = 0; i < num_wires / 2; i++) {
|
||||
EXPECT_EQ(sig[i * 4 + 0].wire, wires[i * 2 + 1]);
|
||||
EXPECT_EQ(sig[i * 4 + 1].wire, wires[i * 2 + 1]);
|
||||
EXPECT_EQ(sig[i * 4 + 2].wire, wires[i * 2 + 1]);
|
||||
EXPECT_EQ(sig[i * 4 + 3].wire, wires[i * 2 + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
// Test multiple sequential removals (simulates removeSignalFromCaseTree)
|
||||
TEST_F(SigSpecRepTest, MultipleSequentialRemovals)
|
||||
{
|
||||
const int num_wires = 512;
|
||||
auto wires = createWires(num_wires);
|
||||
|
||||
// Create many actions, each with one wire
|
||||
std::vector<SigSpec> actions;
|
||||
for (auto w : wires)
|
||||
actions.push_back(SigSpec(w));
|
||||
|
||||
// Remove half the wires from all actions
|
||||
for (int i = 0; i < num_wires / 2; i++) {
|
||||
SigSpec pattern(wires[i]);
|
||||
for (auto &action : actions)
|
||||
if (action.size() > 0)
|
||||
action.remove2(pattern, nullptr);
|
||||
}
|
||||
|
||||
// Verify correct actions were cleared
|
||||
for (int i = 0; i < num_wires; i++) {
|
||||
EXPECT_EQ(actions[i].size(), i < num_wires / 2 ? 0 : 4);
|
||||
}
|
||||
|
||||
// Verify remaining actions contain the correct wires
|
||||
for (int i = num_wires / 2; i < num_wires; i++) {
|
||||
SigSpec expected(wires[i]);
|
||||
EXPECT_EQ(actions[i], expected);
|
||||
}
|
||||
}
|
||||
|
||||
// Test remove2 with very large dataset to check scaling
|
||||
TEST_F(SigSpecRepTest, PoolOverloadLargeDataset)
|
||||
{
|
||||
const int num_wires = 50000;
|
||||
auto wires = createWires(num_wires, 1);
|
||||
SigSpec sig = wiresAsSigSpec(wires);
|
||||
SigSpec other = constsAsSigSpec(num_wires, 1);
|
||||
|
||||
// Remove half
|
||||
pool<SigBit> pattern;
|
||||
for (int i = 0; i < num_wires; i += 2)
|
||||
pattern.insert(SigBit(wires[i], 0));
|
||||
|
||||
EXPECT_EQ(sig.size(), num_wires);
|
||||
sig.remove2(pattern, &other);
|
||||
EXPECT_EQ(sig.size(), num_wires / 2);
|
||||
EXPECT_EQ(other.size(), num_wires / 2);
|
||||
|
||||
// Spot-check: odd-indexed wires should remain
|
||||
for (int i = 0; i < num_wires / 2; i++) {
|
||||
EXPECT_EQ(sig[i].wire, wires[i * 2 + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
// Test set overload (same perf characteristics as pool)
|
||||
TEST_F(SigSpecRepTest, SetOverloadLargeDataset)
|
||||
{
|
||||
const int num_wires = 50000;
|
||||
auto wires = createWires(num_wires, 1);
|
||||
SigSpec sig = wiresAsSigSpec(wires);
|
||||
SigSpec other = constsAsSigSpec(num_wires, 1);
|
||||
|
||||
// Remove half
|
||||
std::set<SigBit> pattern;
|
||||
for (int i = 0; i < num_wires; i += 2)
|
||||
pattern.insert(SigBit(wires[i], 0));
|
||||
|
||||
EXPECT_EQ(sig.size(), num_wires);
|
||||
sig.remove2(pattern, &other);
|
||||
EXPECT_EQ(sig.size(), num_wires / 2);
|
||||
EXPECT_EQ(other.size(), num_wires / 2);
|
||||
|
||||
// Spot-check: odd-indexed wires should remain
|
||||
for (int i = 0; i < num_wires / 2; i++) {
|
||||
EXPECT_EQ(sig[i].wire, wires[i * 2 + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
// Worst case: remove almost all elements
|
||||
TEST_F(SigSpecRepTest, RemoveAlmostAllElements)
|
||||
{
|
||||
const int num_wires = 10000;
|
||||
auto wires = createWires(num_wires, 1);
|
||||
SigSpec sig = wiresAsSigSpec(wires);
|
||||
|
||||
// Remove all but last
|
||||
pool<SigBit> pattern;
|
||||
for (int i = 0; i < num_wires - 1; i++)
|
||||
pattern.insert(SigBit(wires[i], 0));
|
||||
|
||||
EXPECT_EQ(sig.size(), num_wires);
|
||||
sig.remove2(pattern, nullptr);
|
||||
EXPECT_EQ(sig.size(), 1);
|
||||
EXPECT_EQ(sig[0].wire, wires[num_wires - 1]);
|
||||
}
|
||||
|
||||
TEST_F(SigSpecRepTest, EmptyPattern)
|
||||
{
|
||||
auto wires = createWires(1);
|
||||
SigSpec sig(wires[0]);
|
||||
pool<SigBit> empty_pattern;
|
||||
|
||||
EXPECT_EQ(sig.size(), 4);
|
||||
sig.remove2(empty_pattern, nullptr);
|
||||
EXPECT_EQ(sig.size(), 4);
|
||||
|
||||
// Verify the wire is unchanged
|
||||
SigSpec expected(wires[0]);
|
||||
EXPECT_EQ(sig, expected);
|
||||
}
|
||||
|
||||
// Test that NULL wire bits (constants) are not removed
|
||||
TEST_F(SigSpecRepTest, NullWireBitsStay)
|
||||
{
|
||||
auto wires = createWires(1);
|
||||
SigSpec sig;
|
||||
sig.append(wires[0]);
|
||||
sig.append(Const(15, 4));
|
||||
|
||||
// Try to remove the constants
|
||||
pool<SigBit> pattern;
|
||||
SigSpec const_spec(Const(15, 4));
|
||||
for (auto &bit : const_spec)
|
||||
pattern.insert(bit);
|
||||
|
||||
EXPECT_EQ(sig.size(), 8);
|
||||
sig.remove2(pattern, nullptr);
|
||||
EXPECT_EQ(sig.size(), 8); // Constants stay
|
||||
|
||||
// Verify original content is preserved
|
||||
SigSpec expected;
|
||||
expected.append(wires[0]);
|
||||
expected.append(Const(15, 4));
|
||||
EXPECT_EQ(sig, expected);
|
||||
}
|
||||
|
||||
TEST_F(SigSpecRepTest, PartialBitRemoval)
|
||||
{
|
||||
Wire* w = m->addWire("$w1", 8);
|
||||
SigSpec sig(w);
|
||||
|
||||
// Remove bits 2-5
|
||||
pool<SigBit> pattern;
|
||||
for (int i = 2; i < 6; i++)
|
||||
pattern.insert(SigBit(w, i));
|
||||
|
||||
EXPECT_EQ(sig.size(), 8);
|
||||
sig.remove2(pattern, nullptr);
|
||||
EXPECT_EQ(sig.size(), 4);
|
||||
|
||||
// Verify only bits 0,1,6,7 remain
|
||||
EXPECT_EQ(sig[0], SigBit(w, 0));
|
||||
EXPECT_EQ(sig[1], SigBit(w, 1));
|
||||
EXPECT_EQ(sig[2], SigBit(w, 6));
|
||||
EXPECT_EQ(sig[3], SigBit(w, 7));
|
||||
}
|
||||
|
||||
YOSYS_NAMESPACE_END
|
||||
444
tests/unit/kernel/threadingTest.cc
Normal file
444
tests/unit/kernel/threadingTest.cc
Normal file
|
|
@ -0,0 +1,444 @@
|
|||
#include <gtest/gtest.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include "kernel/threading.h"
|
||||
|
||||
YOSYS_NAMESPACE_BEGIN
|
||||
|
||||
class ThreadingTest : public testing::Test {
|
||||
protected:
|
||||
ThreadingTest() {
|
||||
if (log_files.empty())
|
||||
log_files.emplace_back(stdout);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(ThreadingTest, ParallelDispatchThreadPoolCreate) {
|
||||
// Test creating a pool with 0 threads (treated as 1)
|
||||
ParallelDispatchThreadPool pool0(0);
|
||||
EXPECT_EQ(pool0.num_threads(), 1);
|
||||
|
||||
// Test creating a pool with 1 thread
|
||||
ParallelDispatchThreadPool pool1(1);
|
||||
EXPECT_EQ(pool1.num_threads(), 1);
|
||||
|
||||
// Test creating a pool with 2 threads
|
||||
ParallelDispatchThreadPool pool2(2);
|
||||
// YOSYS_MAX_THREADS or system configuration could mean we
|
||||
// decide to only use one thread.
|
||||
EXPECT_GE(pool2.num_threads(), 1);
|
||||
EXPECT_LE(pool2.num_threads(), 2);
|
||||
}
|
||||
|
||||
TEST_F(ThreadingTest, ParallelDispatchThreadPoolRunSimple) {
|
||||
ParallelDispatchThreadPool pool(2);
|
||||
|
||||
std::atomic<int> counter{0};
|
||||
pool.run([&counter](const ParallelDispatchThreadPool::RunCtx &) {
|
||||
counter.fetch_add(1, std::memory_order_relaxed);
|
||||
});
|
||||
|
||||
EXPECT_EQ(counter.load(), pool.num_threads());
|
||||
}
|
||||
|
||||
TEST_F(ThreadingTest, ParallelDispatchThreadPoolRunMultiple) {
|
||||
ParallelDispatchThreadPool pool(2);
|
||||
|
||||
std::atomic<int> counter{0};
|
||||
// Run multiple times to verify the pool can be reused
|
||||
for (int i = 0; i < 5; ++i)
|
||||
pool.run([&counter](const ParallelDispatchThreadPool::RunCtx &) {
|
||||
counter.fetch_add(1, std::memory_order_relaxed);
|
||||
});
|
||||
|
||||
EXPECT_EQ(counter.load(), pool.num_threads() * 5);
|
||||
}
|
||||
|
||||
TEST_F(ThreadingTest, ParallelDispatchThreadPoolRunCtxThreadNums) {
|
||||
ParallelDispatchThreadPool pool(4);
|
||||
|
||||
std::vector<int> thread_nums(pool.num_threads(), -1);
|
||||
pool.run([&thread_nums](const ParallelDispatchThreadPool::RunCtx &ctx) {
|
||||
thread_nums[ctx.thread_num] = ctx.thread_num;
|
||||
});
|
||||
|
||||
// Every thread should have recorded its own thread number
|
||||
for (int i = 0; i < pool.num_threads(); ++i)
|
||||
EXPECT_EQ(thread_nums[i], i);
|
||||
}
|
||||
|
||||
TEST_F(ThreadingTest, ParallelDispatchThreadPoolItemRange) {
|
||||
ParallelDispatchThreadPool pool(3);
|
||||
|
||||
const int num_items = 100;
|
||||
std::vector<std::atomic<int>> item_counts(num_items);
|
||||
for (std::atomic<int> &c : item_counts)
|
||||
c.store(0);
|
||||
|
||||
pool.run([&item_counts](const ParallelDispatchThreadPool::RunCtx &ctx) {
|
||||
for (int i : ctx.item_range(num_items))
|
||||
item_counts[i].fetch_add(1);
|
||||
});
|
||||
|
||||
// Each item should have been processed exactly once
|
||||
for (int i = 0; i < num_items; ++i)
|
||||
EXPECT_EQ(item_counts[i].load(), 1);
|
||||
}
|
||||
|
||||
TEST_F(ThreadingTest, ParallelDispatchThreadPoolSubpool) {
|
||||
ParallelDispatchThreadPool pool(4);
|
||||
|
||||
// Subpool limited to 2 threads
|
||||
ParallelDispatchThreadPool::Subpool subpool(pool, 2);
|
||||
EXPECT_LE(subpool.num_threads(), 2);
|
||||
|
||||
std::atomic<int> counter{0};
|
||||
subpool.run([&counter](const ParallelDispatchThreadPool::RunCtx &) {
|
||||
counter.fetch_add(1, std::memory_order_relaxed);
|
||||
});
|
||||
|
||||
EXPECT_EQ(counter.load(), subpool.num_threads());
|
||||
}
|
||||
|
||||
TEST_F(ThreadingTest, IntRangeIteration) {
|
||||
IntRange range{3, 7};
|
||||
std::vector<int> values;
|
||||
for (int i : range)
|
||||
values.push_back(i);
|
||||
EXPECT_THAT(values, testing::ElementsAre(3, 4, 5, 6));
|
||||
}
|
||||
|
||||
TEST_F(ThreadingTest, IntRangeEmpty) {
|
||||
IntRange range{5, 5};
|
||||
for (int _ : range) {
|
||||
(void)_;
|
||||
FAIL();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ThreadingTest, ItemRangeForWorker) {
|
||||
EXPECT_EQ(item_range_for_worker(10, 0, 3), (IntRange{0, 4}));
|
||||
EXPECT_EQ(item_range_for_worker(10, 1, 3), (IntRange{4, 7}));
|
||||
EXPECT_EQ(item_range_for_worker(10, 2, 3), (IntRange{7, 10}));
|
||||
}
|
||||
|
||||
TEST_F(ThreadingTest, ItemRangeForWorkerZeroThreads) {
|
||||
EXPECT_EQ(item_range_for_worker(10, 0, 0), (IntRange{0, 10}));
|
||||
}
|
||||
|
||||
TEST_F(ThreadingTest, ShardedVectorBasic) {
|
||||
ParallelDispatchThreadPool pool(2);
|
||||
ShardedVector<int> vec(pool);
|
||||
pool.run([&vec](const ParallelDispatchThreadPool::RunCtx &ctx) {
|
||||
vec.insert(ctx, ctx.thread_num * 10);
|
||||
vec.insert(ctx, ctx.thread_num * 10 + 1);
|
||||
});
|
||||
|
||||
EXPECT_FALSE(vec.empty());
|
||||
|
||||
// Count elements
|
||||
std::vector<int> elements;
|
||||
for (int v : vec) {
|
||||
elements.push_back(v);
|
||||
}
|
||||
|
||||
if (pool.num_threads() == 2)
|
||||
EXPECT_THAT(elements, testing::ElementsAre(0, 1, 10, 11));
|
||||
else
|
||||
EXPECT_THAT(elements, testing::ElementsAre(0, 1));
|
||||
}
|
||||
|
||||
TEST_F(ThreadingTest, MonotonicFlagBasic) {
|
||||
MonotonicFlag flag;
|
||||
EXPECT_FALSE(flag.load());
|
||||
flag.set();
|
||||
EXPECT_TRUE(flag.load());
|
||||
flag.set();
|
||||
EXPECT_TRUE(flag.load());
|
||||
}
|
||||
|
||||
TEST_F(ThreadingTest, MonotonicFlagSetAndReturnOld) {
|
||||
MonotonicFlag flag;
|
||||
EXPECT_FALSE(flag.set_and_return_old());
|
||||
EXPECT_TRUE(flag.load());
|
||||
EXPECT_TRUE(flag.set_and_return_old());
|
||||
}
|
||||
|
||||
TEST_F(ThreadingTest, ConcurrentQueueBasic) {
|
||||
ConcurrentQueue<int> queue;
|
||||
queue.push_back(1);
|
||||
queue.push_back(2);
|
||||
queue.push_back(3);
|
||||
|
||||
auto v1 = queue.pop_front();
|
||||
auto v2 = queue.pop_front();
|
||||
auto v3 = queue.pop_front();
|
||||
|
||||
ASSERT_TRUE(v1.has_value());
|
||||
ASSERT_TRUE(v2.has_value());
|
||||
ASSERT_TRUE(v3.has_value());
|
||||
EXPECT_EQ(*v1, 1);
|
||||
EXPECT_EQ(*v2, 2);
|
||||
EXPECT_EQ(*v3, 3);
|
||||
}
|
||||
|
||||
TEST_F(ThreadingTest, ConcurrentQueueTryPopEmpty) {
|
||||
ConcurrentQueue<int> queue;
|
||||
auto v = queue.try_pop_front();
|
||||
EXPECT_FALSE(v.has_value());
|
||||
}
|
||||
|
||||
TEST_F(ThreadingTest, ConcurrentQueueClose) {
|
||||
ConcurrentQueue<int> queue;
|
||||
queue.push_back(42);
|
||||
queue.close();
|
||||
|
||||
// Can still pop existing elements
|
||||
auto v1 = queue.pop_front();
|
||||
ASSERT_TRUE(v1.has_value());
|
||||
EXPECT_EQ(*v1, 42);
|
||||
|
||||
// After close and empty, pop_front returns nullopt
|
||||
auto v2 = queue.pop_front();
|
||||
EXPECT_FALSE(v2.has_value());
|
||||
}
|
||||
|
||||
TEST_F(ThreadingTest, ThreadPoolCreate) {
|
||||
// pool_size of 0 means no worker threads
|
||||
ThreadPool pool0(0, [](int) {});
|
||||
EXPECT_EQ(pool0.num_threads(), 0);
|
||||
|
||||
// pool_size of 1 means 1 worker thread
|
||||
std::atomic<int> counter{0};
|
||||
{
|
||||
ThreadPool pool1(1, [&counter](int thread_num) {
|
||||
EXPECT_EQ(thread_num, 0);
|
||||
counter.fetch_add(1);
|
||||
});
|
||||
}
|
||||
#ifdef YOSYS_ENABLE_THREADS
|
||||
EXPECT_EQ(counter.load(), 1);
|
||||
#else
|
||||
EXPECT_EQ(counter.load(), 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST_F(ThreadingTest, ThreadPoolMultipleThreads) {
|
||||
std::atomic<int> counter{0};
|
||||
{
|
||||
ThreadPool pool(2, [&counter](int) {
|
||||
counter.fetch_add(1);
|
||||
});
|
||||
EXPECT_LE(pool.num_threads(), 2);
|
||||
}
|
||||
#ifdef YOSYS_ENABLE_THREADS
|
||||
EXPECT_GE(counter.load(), 1);
|
||||
EXPECT_LE(counter.load(), 2);
|
||||
#else
|
||||
EXPECT_EQ(counter.load(), 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Helper types for ShardedHashtable tests
|
||||
struct IntValue {
|
||||
using Accumulated = IntValue;
|
||||
int value;
|
||||
operator int() const { return value; }
|
||||
};
|
||||
|
||||
struct IntValueEquality {
|
||||
bool operator()(int a, int b) const { return a == b; }
|
||||
};
|
||||
|
||||
TEST_F(ThreadingTest, ShardedHashtableBasic) {
|
||||
ParallelDispatchThreadPool pool(1);
|
||||
|
||||
using HashSet = ShardedHashtable<IntValue, IntValueEquality, SetCollisionHandler<IntValue>>;
|
||||
HashSet::Builder builder(pool);
|
||||
|
||||
// Insert some values
|
||||
pool.run([&builder](const ParallelDispatchThreadPool::RunCtx &ctx) {
|
||||
builder.insert(ctx, {{10}, 10});
|
||||
builder.insert(ctx, {{20}, 20});
|
||||
builder.insert(ctx, {{30}, 30});
|
||||
});
|
||||
|
||||
// Process
|
||||
pool.run([&builder](const ParallelDispatchThreadPool::RunCtx &ctx) {
|
||||
builder.process(ctx);
|
||||
});
|
||||
|
||||
// Build and lookup
|
||||
HashSet set(builder);
|
||||
const IntValue *found10 = set.find({{10}, 10});
|
||||
const IntValue *found20 = set.find({{20}, 20});
|
||||
const IntValue *found99 = set.find({{99}, 99});
|
||||
|
||||
ASSERT_NE(found10, nullptr);
|
||||
ASSERT_NE(found20, nullptr);
|
||||
EXPECT_EQ(found99, nullptr);
|
||||
EXPECT_EQ(*found10, 10);
|
||||
EXPECT_EQ(*found20, 20);
|
||||
}
|
||||
|
||||
TEST_F(ThreadingTest, ShardedHashtableParallelInsert) {
|
||||
ParallelDispatchThreadPool pool(3);
|
||||
|
||||
using HashSet = ShardedHashtable<IntValue, IntValueEquality, SetCollisionHandler<IntValue>>;
|
||||
HashSet::Builder builder(pool);
|
||||
|
||||
// Insert values from multiple threads
|
||||
pool.run([&builder](const ParallelDispatchThreadPool::RunCtx &ctx) {
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
int val = ctx.thread_num * 100 + i;
|
||||
builder.insert(ctx, {{val}, static_cast<unsigned>(val)});
|
||||
}
|
||||
});
|
||||
|
||||
pool.run([&builder](const ParallelDispatchThreadPool::RunCtx &ctx) {
|
||||
builder.process(ctx);
|
||||
});
|
||||
|
||||
HashSet set(builder);
|
||||
|
||||
// Verify all values can be found
|
||||
for (int t = 0; t < pool.num_threads(); ++t) {
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
int val = t * 100 + i;
|
||||
const IntValue *found = set.find({{val}, static_cast<unsigned>(val)});
|
||||
ASSERT_NE(found, nullptr) << "Value " << val << " not found";
|
||||
EXPECT_EQ(*found, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper types for ShardedHashtable tests
|
||||
struct IntDictValue {
|
||||
using Accumulated = IntDictValue;
|
||||
int key;
|
||||
int value;
|
||||
bool operator==(const IntDictValue &other) const { return key == other.key && value == other.value; }
|
||||
bool operator!=(const IntDictValue &other) const { return !(*this == other); }
|
||||
};
|
||||
|
||||
struct IntDictKeyEquality {
|
||||
bool operator()(const IntDictValue &a, const IntDictValue &b) const { return a.key == b.key; }
|
||||
};
|
||||
|
||||
// Collision handler that sums values
|
||||
struct SumCollisionHandler {
|
||||
void operator()(IntDictValue &existing, IntDictValue &incoming) const {
|
||||
existing.value += incoming.value;
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(ThreadingTest, ShardedHashtableCollision) {
|
||||
ParallelDispatchThreadPool pool(1);
|
||||
|
||||
using HashSet = ShardedHashtable<IntDictValue, IntDictKeyEquality, SumCollisionHandler>;
|
||||
HashSet::Builder builder(pool);
|
||||
|
||||
// Insert duplicate keys with same hash - duplicates should collapse
|
||||
pool.run([&builder](const ParallelDispatchThreadPool::RunCtx &ctx) {
|
||||
builder.insert(ctx, {{5, 10}, 5});
|
||||
builder.insert(ctx, {{5, 12}, 5}); // Duplicate key/hash
|
||||
builder.insert(ctx, {{5, 14}, 5}); // Another duplicate
|
||||
});
|
||||
|
||||
pool.run([&builder](const ParallelDispatchThreadPool::RunCtx &ctx) {
|
||||
builder.process(ctx);
|
||||
});
|
||||
|
||||
HashSet set(builder);
|
||||
const IntDictValue *found = set.find({{5, 0}, 5});
|
||||
ASSERT_NE(found, nullptr);
|
||||
// With default collision handler, first value is kept
|
||||
EXPECT_EQ(*found, (IntDictValue{5, 36}));
|
||||
}
|
||||
|
||||
TEST_F(ThreadingTest, ShardedHashtableEmpty) {
|
||||
ParallelDispatchThreadPool pool(1);
|
||||
|
||||
using HashSet = ShardedHashtable<IntValue, IntValueEquality, SetCollisionHandler<IntValue>>;
|
||||
HashSet::Builder builder(pool);
|
||||
|
||||
// Don't insert anything, just process
|
||||
pool.run([&builder](const ParallelDispatchThreadPool::RunCtx &ctx) {
|
||||
builder.process(ctx);
|
||||
});
|
||||
|
||||
HashSet set(builder);
|
||||
const IntValue *found = set.find({{42}, 42});
|
||||
EXPECT_EQ(found, nullptr);
|
||||
}
|
||||
|
||||
TEST_F(ThreadingTest, ConcurrentWorkQueueSingleThread) {
|
||||
ConcurrentWorkQueue<int> queue(1, 10); // 1 thread, batch size 10
|
||||
EXPECT_EQ(queue.num_threads(), 1);
|
||||
|
||||
ThreadIndex thread{0};
|
||||
|
||||
// Push some items (less than batch size)
|
||||
for (int i = 0; i < 5; ++i)
|
||||
queue.push(thread, i);
|
||||
|
||||
// Pop should return those items
|
||||
std::vector<int> batch = queue.pop_batch(thread);
|
||||
EXPECT_THAT(batch, testing::UnorderedElementsAre(0, 1, 2, 3, 4));
|
||||
|
||||
// Next pop should return empty (all threads "waiting")
|
||||
std::vector<int> empty_batch = queue.pop_batch(thread);
|
||||
EXPECT_TRUE(empty_batch.empty());
|
||||
}
|
||||
|
||||
TEST_F(ThreadingTest, ConcurrentWorkQueueBatching) {
|
||||
ConcurrentWorkQueue<int> queue(1, 3); // batch size 3
|
||||
ThreadIndex thread{0};
|
||||
|
||||
queue.push(thread, 10);
|
||||
queue.push(thread, 20);
|
||||
queue.push(thread, 30);
|
||||
queue.push(thread, 40);
|
||||
queue.push(thread, 50);
|
||||
|
||||
std::vector<int> popped;
|
||||
while (true) {
|
||||
std::vector<int> batch = queue.pop_batch(thread);
|
||||
if (batch.empty())
|
||||
break;
|
||||
popped.insert(popped.end(), batch.begin(), batch.end());
|
||||
}
|
||||
EXPECT_THAT(popped, testing::UnorderedElementsAre(10, 20, 30, 40, 50));
|
||||
}
|
||||
|
||||
TEST_F(ThreadingTest, ConcurrentWorkQueueParallel) {
|
||||
ParallelDispatchThreadPool pool(2);
|
||||
if (pool.num_threads() < 2) {
|
||||
// Skip test if we don't have multiple threads
|
||||
return;
|
||||
}
|
||||
|
||||
ConcurrentWorkQueue<int> queue(2, 3);
|
||||
std::atomic<int> sum{0};
|
||||
|
||||
pool.run([&queue, &sum](const ParallelDispatchThreadPool::RunCtx &ctx) {
|
||||
// Each thread pushes some work
|
||||
for (int i = 0; i < 10; ++i)
|
||||
queue.push(ctx, ctx.thread_num * 100 + i);
|
||||
|
||||
// Each thread processes work until done
|
||||
while (true) {
|
||||
std::vector<int> batch = queue.pop_batch(ctx);
|
||||
if (batch.empty())
|
||||
break;
|
||||
for (int v : batch)
|
||||
sum.fetch_add(v);
|
||||
}
|
||||
});
|
||||
|
||||
// Thread 0 pushes: 0+1+2+...+9 = 45
|
||||
// Thread 1 pushes: 100+101+...+109 = 1045
|
||||
// Total = 45 + 1045 = 1090
|
||||
EXPECT_EQ(sum.load(), 1090);
|
||||
}
|
||||
|
||||
YOSYS_NAMESPACE_END
|
||||
5
tests/unit/opt/CMakeLists.txt
Normal file
5
tests/unit/opt/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
yosys_gtest(opt
|
||||
optDffFindComplementaryPatternTest.cc
|
||||
COMPONENTS
|
||||
opt_dff
|
||||
)
|
||||
5
tests/unit/techmap/CMakeLists.txt
Normal file
5
tests/unit/techmap/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
yosys_gtest(techmap
|
||||
libparseTest.cc
|
||||
COMPONENTS
|
||||
libparse
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue