I am working on a project in SystemC and want to incorporate unit testing. Is it possible to use existing unit test frameworks with SystemC?
I ask this because it seems
Very often SystemC Device-under-test (DUT) can be resetted to initial state by asserting some signal. You can utilize this fact and enable any C++ unit testing framework you want. Just reset you DUT before running each test, so you don't need to elaborate it twice.
Here is an example with Google Test, and a simple "Accumulator" DUT
::testing::InitGoogleTest(&argc, argv);
) from sc_main
sc_module
by calling RUN_ALL_TESTS()
Source:
#include <systemc.h>
#include "gtest/gtest.h"
struct test_driver;
test_driver *test_driver_p = nullptr;
void register_test_driver(test_driver *td) {
test_driver_p = td;
}
test_driver* get_test_driver() {
assert(test_driver_p);
return test_driver_p;
}
SC_MODULE(dut_accum) {
sc_in_clk clk{"clk"};
sc_in<bool> reset{"reset"};
sc_in<bool> en{"en"};
sc_in<int> din{"din"};
sc_out<int> dout{"dout"};
SC_CTOR(dut_accum) {
SC_METHOD(accum_method);
sensitive << clk.pos();
};
void accum_method() {
if (reset)
dout = 0;
else if (en)
dout = dout + din;
}
};
SC_MODULE(test_driver) {
sc_signal<bool> reset{"reset",1};
sc_signal<bool> en{"en",0};
sc_signal<int> din{"din",0};
sc_signal<int> dout{"dout"};
SC_CTOR(test_driver) {
dut_inst.clk(clk);
dut_inst.reset(reset);
dut_inst.en(en);
dut_inst.din(din);
dut_inst.dout(dout);
SC_THREAD(test_thread);
sensitive << clk.posedge_event();
register_test_driver(this);
}
private:
void test_thread() {
if (RUN_ALL_TESTS())
SC_REPORT_ERROR("Gtest", "Some test FAILED");
sc_stop();
}
dut_accum dut_inst{"dut_inst"};
sc_clock clk{"clk", 10, SC_NS};
};
namespace {
// The fixture for testing dut_accum
class accum_test: public ::testing::Test {
protected:
test_driver & td;
accum_test(): td(*get_test_driver()){
reset_dut();
}
virtual ~accum_test() {}
void reset_dut(){
td.reset = 1;
wait();
td.reset = 0;
}
};
TEST_F(accum_test, test0) {
td.din = 10;
td.en = 1;
wait();
wait();
EXPECT_EQ(td.dout.read(), 10);
}
TEST_F(accum_test, test1_no_en) {
td.din = 10;
td.en = 0;
wait();
wait();
EXPECT_EQ(td.dout.read(), 10); // this test will fail, since en is 0
}
TEST_F(accum_test, test2_reset_asserted) {
td.din = 10;
td.en = 1;
td.reset = 1;
wait();
wait();
EXPECT_EQ(td.dout.read(), 0);
}
}
int sc_main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
test_driver td{"td"};
sc_start();
}
CMakeLists.txt (requires installed SystemC 2.3.2 )
cmake_minimum_required(VERSION 3.8)
project(systemc_gtest)
find_package(SystemCLanguage CONFIG REQUIRED)
set (CMAKE_CXX_STANDARD ${SystemC_CXX_STANDARD})
find_package(GTest REQUIRED)
enable_testing()
add_executable(systemc_gtest main.cpp)
target_link_libraries(systemc_gtest ${GTEST_LIBRARIES} SystemC::systemc )
target_include_directories(systemc_gtest PRIVATE ${GTEST_INCLUDE_DIRS})
add_test(AllTestsInSystemCGtest systemc_gtest)
I have a second solution to this question that uses CMkae and CTest (http://cmake.org/). The setup I used creates a binary for each test. Here's the CMakeLists.txt
file I used:
project(sc_unit_test)
include_directories(/home/stephan/local/include)
find_library(systemc systemc /home/stephan/local/lib-linux64)
link_directories(/home/stephan/local/lib-linux64)
add_executable(test_1 test_1.cxx)
target_link_libraries(test_1 systemc)
add_executable(test_2 test_2.cxx)
target_link_libraries(test_2 systemc)
enable_testing()
add_test(test_1 test_1)
add_test(test_2 test_2)
Each test_*.cxx
file has a sc_main
method that executes the test and the return value indicates whether or not the test passed or failed. To run the tests simply do:
$ cmake .
$ make
$ ctest
Test project
1/ 2 Testing test_1 Passed
2/ 2 Testing test_2 Passed
100% tests passed, 0 tests failed out of 2
If you don't want to run the simulator, you can simply skip the call to sc_start
and exit the application after doing whatever specific testing you want on a particular module.
You must create all necessary SystemC signals, SystemC modules and make connection between them before you run any test in GTest. This requires to create own gtest_main.cc
implementation. Naturally in SystemC you must put everything in sc_main
function.
For this, I would use registry design pattern.
First create registry class (registry + factory + singleton). This class will be responsible for storing registered constructors using dynamic allocation with new and smart pointer in lambda expression (see factory::add
class). Create all objects using factory::create()
method before running all tests. Then you can get object using factory::get()
method in you test execution.
factory.hpp
#ifndef FACTORY_HPP
#define FACTORY_HPP
#include <map>
#include <string>
#include <memory>
#include <functional>
class factory {
public:
static factory& get_instance();
template<typename T, typename ...Args>
class add {
public:
add(Args&&... args);
add(const std::string& name, Args&&... args);
};
template<typename T>
static T* get(const std::string& name = "");
void create();
void destroy();
private:
using destructor = std::function<void(void*)>;
using object = std::unique_ptr<void, destructor>;
using constructor = std::function<object(void)>;
factory();
factory(const factory& other) = delete;
factory& operator=(const factory& other) = delete;
void add_object(const std::string& name, constructor create);
void* get_object(const std::string& name);
std::map<std::string, constructor> m_constructors;
std::map<std::string, object> m_objects;
};
template<typename T, typename ...Args>
factory::add<T, Args...>::add(Args&&... args) {
add("", args...);
}
template<typename T, typename ...Args>
factory::add<T, Args...>::add(const std::string& name, Args&&... args) {
factory::get_instance().add_object(name,
[args...] () -> object {
return object{
new T(std::forward<Args>(args)...),
[] (void* obj) {
delete static_cast<T*>(obj);
}
};
}
);
}
template<typename T> auto
factory::get(const std::string& name) -> T* {
return static_cast<T*>(factory::get_instance().get_object(name));
}
#endif /* FACTORY_HPP */
factory.cpp
#include "factory.hpp"
#include <stdexcept>
auto factory::get_instance() -> factory& {
static factory instance{};
return instance;
}
factory::factory() :
m_constructors{},
m_objects{}
{ }
void factory::create() {
for (const auto& item : m_constructors) {
m_objects[item.first] = item.second();
}
}
void factory::destroy() {
m_objects.clear();
}
void factory::add_object(const std::string& name, constructor create) {
auto it = m_constructors.find(name);
if (it == m_constructors.cend()) {
m_constructors[name] = create;
}
else {
throw std::runtime_error("factory::add(): "
+ name + " object already exist in factory");
}
}
auto factory::get_object(const std::string& name) -> void* {
auto it = m_objects.find(name);
if (it == m_objects.cend()) {
throw std::runtime_error("factory::get(): "
+ name + " object doesn't exist in factory");
}
return it->second.get();
}
Create your own version of gtest_main.cc
implementation. Call factory::create()
method to create all SystemC signals and SystemC modules before running any tests RUN_ALL_TESTS()
. Because factory class is a singleton design pattern, call factory::destroy()
method after finishing all tests to destroy all created SystemC objects.
main.cpp
#include "factory.hpp"
#include <systemc>
#include <gtest/gtest.h>
int sc_main(int argc, char* argv[]) {
factory::get_instance().create();
testing::InitGoogleTest(&argc, argv);
int status = RUN_ALL_TESTS();
factory::get_instance().destroy();
return status;
}
Then define dut class in your test than will create SystemC signals and SystemC modules. In constructor do connection between created SystemC signals and modules. Register defined dut class to registry object using global constructor like this factory::add g. After than you can get your dut object using simple factory::get() method.
test.cpp
#include "my_module.h"
#include "factory.hpp"
#include <gtest/gtest.h>
#include <systemc>
class dut {
public:
sc_core::sc_clock aclk{"aclk"};
sc_core::sc_signal<bool> areset_n{"areset_n"};
sc_core::sc_signal<bool> in{"in"};
sc_core::sc_signal<bool> out{"out"};
dut() {
m_dut.aclk(aclk);
m_dut.areset_n(areset_n);
m_dut.in(in);
m_dut.out(out);
}
private:
my_module m_dut{"my_module"};
};
static factory::add<dut> g;
TEST(my_module, simple) {
auto test = factory::get<dut>();
test->areset_n = 0;
test->in = 0;
sc_start(3, SC_NS);
test->areset_n = 1;
test->in = 1;
sc_start(3, SC_NS);
EXPECT_TRUE(test->out.read());
}
my_module.h
#ifndef MY_MODULE_H
#define MY_MODULE_H
#include <systemc>
struct my_module : public sc_core::sc_module {
my_module(const sc_core::sc_module_name& name): sc_core::sc_module(name) {
SC_HAS_PROCESS(my_module);
SC_METHOD(flip_flop_impl);
sensitive << aclk.pos();
<< areset_n.neg();
dont_initialize();
}
void flip_flop_impl() {
if(areset_n.read()) {
out.write(in.read());
} else {
out.write(false);
}
}
sc_core::sc_in<bool> aclk{"aclk"};
sc_core::sc_in<bool> areset_n{"areset_n"};
sc_core::sc_in<bool> in{"in"};
sc_core::sc_out<bool> out{"out"};
}; //< my_module
#endif /* MY_MODULE_H */
CMakeLists.txt
cmake_minimum_required(VERSION 3.5)
project(factory_gtest)
find_package(SystemCLanguage CONFIG REQUIRED)
set(CMAKE_CXX_STANDARD ${SystemC_CXX_STANDARD})
find_package(GTest REQUIRED)
enable_testing()
add_executable(${PROJECT_NAME} main.cpp factory.cpp test.cpp)
target_link_libraries(${PROJECT_NAME} ${GTEST_LIBRARIES} SystemC::systemc)
target_include_directories(${PROJECT_NAME} PRIVATE ${GTEST_INCLUDE_DIRS})
add_test(SystemCGTestExample ${PROJECT_NAME})
For more inspiration, you can check my logic library for SystemC verification: https://github.com/tymonx/logic
I was able to run 2 SystemC tests using the fork system call. I used the tutorial example on doulos.com and the Google Test framework. I was able to run the test twice, but I get an error printed out by the SystemC simulator about starting the test after calling sc_stop. However, regardless of the error, the simulator runs fine the second time around.
SystemC 2.2.0 --- Feb 24 2011 15:01:50
Copyright (c) 1996-2006 by all Contributors
ALL RIGHTS RESERVED
Running main() from gtest_main.cc
[==========] Running 2 tests from 1 test case.
[----------] Global test environment set-up.
[----------] 2 tests from systemc_test
[ RUN ] systemc_test.test1
Time A B F
0 s 0 0 0
0 s 0 0 1
10 ns 0 1 1
20 ns 1 0 1
30 ns 1 1 0
SystemC: simulation stopped by user.
[ OK ] systemc_test.test1 (1 ms)
[ RUN ] systemc_test.test2
Error: (E546) sc_start called after sc_stop has been called
In file: ../../../../src/sysc/kernel/sc_simcontext.cpp:1315
[ OK ] systemc_test.test2 (2 ms)
[----------] 2 tests from systemc_test (3 ms total)
[----------] Global test environment tear-down
[==========] 2 tests from 1 test case ran. (3 ms total)
[ PASSED ] 2 tests.
[ OK ] systemc_test.test1 (3 ms)
[ RUN ] systemc_test.test2
Time A B F
0 s 0 0 0
0 s 0 0 1
10 ns 0 1 1
20 ns 1 0 1
30 ns 1 1 0
SystemC: simulation stopped by user.
[ OK ] systemc_test.test2 (1 ms)
[----------] 2 tests from systemc_test (4 ms total)
[----------] Global test environment tear-down
[==========] 2 tests from 1 test case ran. (4 ms total)
[ PASSED ] 2 tests.
[ OK ] systemc_test.test2 (1 ms)
[----------] 2 tests from systemc_test (4 ms total)
[----------] Global test environment tear-down
[==========] 2 tests from 1 test case ran. (4 ms total)
[ PASSED ] 2 tests.
UPDATE: Code sample as requested:
// main_1.cxx
#include "systemc.h"
#include "stim.hxx"
#include "exor2.hxx"
#include "mon.hxx"
//#include <pthread.h>
#include <sys/types.h>
#include <sys/wait.h>
void run_1()
{
sc_signal<bool> ASig, BSig, FSig;
sc_clock TestClk("TestClock", 10, SC_NS,0.5);
stim* Stim1 = new stim("Stimulus1_1");
Stim1->A(ASig);
Stim1->B(BSig);
Stim1->Clk(TestClk);
exor2* DUT = new exor2("exor2_1");
DUT->A(ASig);
DUT->B(BSig);
DUT->F(FSig);
mon* Monitor1 = new mon("Monitor_1");
Monitor1->A(ASig);
Monitor1->B(BSig);
Monitor1->F(FSig);
Monitor1->Clk(TestClk);
Stim1->run();
delete Stim1;
delete DUT;
delete Monitor1;
}
bool sc_main_1()
{
//int rc;
//pthread_t thread;
//if( (rc = pthread_create( &thread, NULL, &run_1, NULL)) )
//{
// printf("Thread creation failed: %d\n", rc);
//};
//pthread_join(thread, NULL);
int pid = fork();
if(pid == 0)
{
run_1();
};
waitpid(pid, NULL, 0);
return true;
};
// main_2.cxx
#include "systemc.h"
#include "stim.hxx"
#include "exor2.hxx"
#include "mon.hxx"
//#include <pthread.h>
#include <sys/types.h>
#include <sys/wait.h>
void run_2()
{
sc_signal<bool> ASig, BSig, FSig;
sc_clock TestClk("TestClock", 10, SC_NS,0.5);
stim* Stim1 = new stim("Stimulus1_2");
Stim1->A(ASig);
Stim1->B(BSig);
Stim1->Clk(TestClk);
exor2* DUT = new exor2("exor2_2");
DUT->A(ASig);
DUT->B(BSig);
DUT->F(FSig);
mon* Monitor1 = new mon("Monitor_2");
Monitor1->A(ASig);
Monitor1->B(BSig);
Monitor1->F(FSig);
Monitor1->Clk(TestClk);
Stim1->run();
delete Stim1;
delete DUT;
delete Monitor1;
}
bool sc_main_2()
{
//int rc;
//pthread_t thread;
//if( (rc = pthread_create( &thread, NULL, &run_1, NULL)) )
//{
// printf("Thread creation failed: %d\n", rc);
//};
//pthread_join(thread, NULL);
int pid = fork();
if(pid == 0)
{
run_2();
};
waitpid(pid, NULL, 0);
return true;
};
// main.cxx
#include "systemc.h"
#include "gtest/gtest.h"
extern bool sc_main_1();
extern bool sc_main_2();
TEST(systemc_test, test1)
{
EXPECT_TRUE(sc_main_1());
};
TEST(systemc_test, test2)
{
EXPECT_TRUE(sc_main_2());
};
int sc_main(int argc, char* argv[])
{
std::cout << "Running main() from gtest_main.cc\n";
testing::InitGoogleTest(&argc, argv);
RUN_ALL_TESTS();
return 0;
}
// stim.hxx
#ifndef stim_hxx
#define stim_hxx
#include "systemc.h"
SC_MODULE(stim)
{
sc_out<bool> A, B;
sc_in<bool> Clk;
void StimGen()
{
A.write(false);
B.write(false);
wait();
A.write(false);
B.write(true);
wait();
A.write(true);
B.write(false);
wait();
A.write(true);
B.write(true);
wait();
sc_stop();
}
SC_CTOR(stim)
{
SC_THREAD(StimGen);
sensitive << Clk.pos();
}
bool run()
{
sc_start(); // run forever
return true;
};
};
#endif
// exor2.hxx
#ifndef exor_hxx
#define exor_hxx
#include "systemc.h"
#include "nand2.hxx"
SC_MODULE(exor2)
{
sc_in<bool> A, B;
sc_out<bool> F;
nand2 n1, n2, n3, n4;
sc_signal<bool> S1, S2, S3;
SC_CTOR(exor2) : n1("N1"), n2("N2"), n3("N3"), n4("N4")
{
n1.A(A);
n1.B(B);
n1.F(S1);
n2.A(A);
n2.B(S1);
n2.F(S2);
n3.A(S1);
n3.B(B);
n3.F(S3);
n4.A(S2);
n4.B(S3);
n4.F(F);
}
};
#endif
// mon.hxx
#ifndef mon_hxx
#define mon_hxx
#include "systemc.h"
#include <iomanip>
#include <iostream>
using namespace std;
SC_MODULE(mon)
{
sc_in<bool> A,B,F;
sc_in<bool> Clk;
void monitor()
{
cout << setw(10) << "Time";
cout << setw(2) << "A" ;
cout << setw(2) << "B";
cout << setw(2) << "F" << endl;
while (true)
{
cout << setw(10) << sc_time_stamp();
cout << setw(2) << A.read();
cout << setw(2) << B.read();
cout << setw(2) << F.read() << endl;
wait(); // wait for 1 clock cycle
}
}
SC_CTOR(mon)
{
SC_THREAD(monitor);
sensitive << Clk.pos();
}
};
#endif