【问题标题】:Using existing unit test frameworks with SystemC在 SystemC 中使用现有的单元测试框架
【发布时间】:2019-05-10 16:00:05
【问题描述】:

我正在从事 SystemC 中的一个项目,并希望加入单元测试。是否可以在 SystemC 中使用现有的单元测试框架?

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

【问题讨论】:

    标签: unit-testing systemc


    【解决方案1】:

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

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

    首先创建注册表类(注册表+工厂+单例)。此类将负责在 lambda 表达式中使用带有 new 和智能指针的动态分配存储已注册的构造函数(请参阅factory::add 类)。在运行所有测试之前使用factory::create() 方法创建所有对象。然后您可以在测试执行中使用factory::get()method 获取对象。

    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 信号和模块之间建立连接。使用像 factory::add g 这样的全局构造函数将定义的 dut 类注册到注册表对象。之后,您可以使用简单的 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})
    

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

    【讨论】:

      【解决方案2】:

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

      这是一个使用 Google 测试的示例,以及一个简单的“累加器”DUT

      1. sc_main 初始化 GTest (::testing::InitGoogleTest(&amp;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)
      

      【讨论】:

      【解决方案3】:

      我能够使用 fork 系统调用运行 2 个 SystemC 测试。我在doulos.comGoogle Test 框架上使用了教程示​​例。我能够运行测试两次,但是 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
      

      【讨论】:

      • 感谢您的回复!您能否提供一些带有 Google 测试框架的 systemC 代码?
      • 也许你可以使用进程而不是线程,这样 systemc 模拟器就不会报错。
      • 我遇到了同样的问题(SystemC 抱怨 sc_start() 被多次调用)。万一你有兴趣here你可以找到我原来的问题和我想出的解决方案。
      【解决方案4】:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-09-10
        • 2023-04-10
        • 2023-03-11
        相关资源
        最近更新 更多