Использование существующих структур модульных тестов с SystemC - PullRequest
11 голосов
/ 07 февраля 2011

Я работаю над проектом в SystemC и хочу включить модульное тестирование.Можно ли использовать существующие платформы модульного тестирования с SystemC?

Я спрашиваю об этом, потому что кажется, что модули SystemC выполняются только с ядром моделирования, и я хочу использовать модульные тесты на самих модулях.*

Ответы [ 4 ]

4 голосов
/ 19 ноября 2017

Вы должны создать все необходимые сигналы SystemC, модули SystemC и установить между ними связь, прежде чем запускать любой тест в GTest.Это требует создания собственной реализации gtest_main.cc.Естественно, в SystemC вы должны поместить все в sc_main функцию.

Для этого я бы использовал шаблон проектирования реестра.

Сначала создайте класс реестра (Registry + Factory + Singleton).Этот класс будет отвечать за хранение зарегистрированных конструкторов с использованием динамического выделения с новым и умным указателем в лямбда-выражении (см. factory::add класс).Создайте все объекты, используя метод factory::create() перед запуском всех тестов.Затем вы можете получить объект, используя метод factory::get() при выполнении теста.

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();
}

Создайте собственную версию реализации gtest_main.cc.Вызовите метод factory::create() для создания всех сигналов SystemC и модулей SystemC перед выполнением любых тестов RUN_ALL_TESTS().Поскольку фабричный класс является одноэлементным шаблоном проектирования, после завершения всех тестов вызовите метод factory::destroy(), чтобы уничтожить все созданные объекты SystemC.

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;
}

Затемопределите класс dut в своем тесте, чем создадите сигналы SystemC и модули SystemC.В конструкторе сделайте связь между созданными сигналами SystemC и модулями.Зарегистрируйте определенный dut класс в объекте реестра, используя глобальный конструктор, подобный этому factory :: add g .После этого вы можете получить свой объект, используя простой метод factory :: get () .

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})

Для получения дополнительной информации вы можете проверить мою библиотеку logic для проверки SystemC: https://github.com/tymonx/logic

2 голосов
/ 30 мая 2012

У меня есть второе решение этого вопроса, которое использует CMkae и CTest (http://cmake.org/). Используемая мной установка создает двоичный файл для каждого теста. Вот файл CMakeLists.txt, который я использовал:

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)

В каждом файле test_*.cxx есть метод sc_main, который выполняет тест, а возвращаемое значение указывает, пройден тест или нет. Чтобы запустить тесты, просто выполните:

$ 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

Если вы неЕсли вы не хотите запускать симулятор, вы можете просто пропустить вызов на sc_start и выйти из приложения после выполнения любого конкретного тестирования, которое вы хотите на конкретном модуле.

2 голосов
/ 22 сентября 2017

Очень часто тестируемое устройство SystemC (DUT) может быть сброшено в исходное состояние путем подачи некоторого сигнала. Вы можете использовать этот факт и включить любую среду модульного тестирования C ++, какую пожелаете. Просто сбросьте DUT перед выполнением каждого теста, так что вам не нужно разрабатывать его дважды.

Вот пример с Google Test и простым «Аккумулятором» DUT

  1. Инициализировать GTest (::testing::InitGoogleTest(&argc, argv);) из sc_main
  2. Разработайте вашу модель
  3. Запускать тесты из некоторого потока внутри sc_module, вызывая RUN_ALL_TESTS()
  4. Вам нужно каким-то образом передать указатель на интерфейс SystemC DUT в ваши тесты. Я использовал глобальную переменную для этой цели

Источник:

#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 (требуется установленный 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)
2 голосов
/ 28 февраля 2011

Мне удалось выполнить 2 теста SystemC с помощью системного вызова fork. Я использовал учебный пример на doulos.com и Google Test framework. Я смог выполнить тест дважды, но на симуляторе SystemC я получил сообщение об ошибке при запуске теста после вызова sc_stop. Однако, независимо от ошибки, симулятор работает нормально во второй раз.

 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.

ОБНОВЛЕНИЕ: Пример кода по запросу:

// 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
...