Jump to content
katang

Unit testing with gtest

Recommended Posts

with reference to

http://stackoverflow.com/questions/4923292/using-existing-unit-test-frameworks-with-systemc

the

Error: (E546) sc_start called after sc_stop has been called

seems to be logical. Can it have side effects, should (can) it be suppressed, or can the system be re-initialized, or is a better way for unit testing?

A related question: in a testing environment it would be advantageous for me, to use some SystemC-handled functions as normal C++ function (I mean to make some unit tests). I guess BEFORE calling sc_start, I can do so. Am I right, or they may have side effects? Is it safe to make unit tests together with SystemC-style testing, or which are the limitations?

Share this post


Link to post
Share on other sites

Once sc_stop() has been called, you are not allowed to call sc_start() anymore (cf. to clause 4.5.3 in IEEE Std 1666-2011). For each unit test involving a sc_start() and sc_stop() sequence, you will have to start an own process to ensure that the SystemC kernel is properly initialized. Testing frameworks, which link together individual unit test into one big executable containing the test runner are to my knowledge not suitable for unit testing SystemC applications. The reason is that the simulation kernel of the SystemC PoC simulator can currently not be restarted due to some implementation constraints. However, you can use script-based test runner approaches, e.g., provided by CMake/CTest.

Once you link to libsystemc, you will also have to provide a sc_main() function. Most SystemC functions will only properly work in the context of sc_main(), especially if they manipulate to design hierarchy (anything related to sc_object or control elaboration/simulation), You can check the SystemC regression test suite for examples on how to do unit testing in the SystemC context.

Share this post


Link to post
Share on other sites

You must create all necessary SystemC signals, SystemC modules and make connection between them before you run any test in gtest. This require 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<dut> g. After than you can get your dut object using simple factory::get<dut>() 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());
}

For more inspiration, you can check my logic library for SystemC verification: https://github.com/tymonx/logic

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×