将现有的单元测试框架与 SystemC 结合使用

发布于 2024-10-16 09:20:58 字数 126 浏览 2 评论 0原文

我正在使用 SystemC 开发一个项目,并且想要合并单元测试。是否可以将现有的单元测试框架与 SystemC 一起使用?

我问这个问题是因为 SystemC 模块似乎只能通过模拟内核执行,并且我想对模块本身使用单元测试。

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 like the SystemC modules only get executed with the simulation kernel, and I want to use unit tests on the modules themselves.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

冷弦 2024-10-23 09:20:58

在 GTest 中运行任何测试之前,您必须创建所有必需的 SystemC 信号、SystemC 模块并在它们之间建立连接。这需要创建自己的 gtest_main.cc 实现。当然,在 SystemC 中,您必须将所有内容放入 sc_main 函数中。

为此,我将使用注册表设计模式。

首先创建注册表类(注册表+工厂+单例)。该类将负责使用 lambda 表达式中的 new 和智能指针进行动态分配来存储已注册的构造函数(请参阅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 实现版本。在运行任何测试RUN_ALL_TESTS()之前,调用factory::create()方法创建所有SystemC信号和SystemC模块。由于工厂类是单例设计模式,因此在完成所有测试后调用factory::destroy()方法来销毁所有创建的SystemC对象。

ma​​in.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() 方法获取 dut 对象。

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

如需更多灵感,您可以查看我的用于 SystemC 验证的 logic 库: https://github.com/tymonx/logic

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

感性不性感 2024-10-23 09:20:58

我能够使用 fork 系统调用运行 2 个 SystemC 测试。我使用了 doulos.comGoogle 测试框架。我能够运行测试两次,但 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

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
难忘№最初的完美 2024-10-23 09:20:58

我对这个问题有第二个解决方案,它使用 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 的调用,并在对特定模块进行所需的任何特定测试后退出应用程序。

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.

方觉久 2024-10-23 09:20:58

通常,SystemC 被测设备 (DUT) 可以通过断言某些信号来重置为初始状态。您可以利用这一事实并启用您想要的任何 C++ 单元测试框架。只需在运行每个测试之前重置您的 DUT,因此您无需对其进行两次详细说明。

以下是 Google Test 的示例,以及来自 sc_main 的简单“累加器”DUT

  1. GTest (::testing::InitGoogleTest(&argc, argv);)
  2. 初始化 您的模型
  3. 通过调用 RUN_ALL_TESTS()sc_module 内的某个线程运行测试
  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)

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

  1. Initialize GTest (::testing::InitGoogleTest(&argc, argv);) from sc_main
  2. Elaborate your model
  3. Run tests from some thread inside sc_module by calling RUN_ALL_TESTS()
  4. You will need somehow to pass pointer to SystemC DUT interface to your tests. I've used global variable for that purpose

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)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文