2011-02-07 19 views
6

Sto lavorando a un progetto in SystemC e desidero incorporare il test dell'unità. È possibile utilizzare i quadri di test unitari esistenti con SystemC?Utilizzo di framework di test unità esistenti con SystemC

Chiedo questo perché sembra che i moduli SystemC vengano eseguiti solo con il kernel di simulazione e voglio utilizzare i test unitari sui moduli stessi.

risposta

2

Sono stato in grado di eseguire 2 test SystemC utilizzando la chiamata di sistema fork. Ho usato l'esempio tutorial su doulos.com e il framework Google Test. Sono stato in grado di eseguire il test due volte, ma ricevo un errore stampato dal simulatore SystemC sull'avvio del test dopo aver chiamato sc_stop. Tuttavia, indipendentemente dall'errore, il simulatore funziona correttamente la seconda volta.

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: Esempio di codice come richiesto:

// 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 
+0

Grazie per la risposta! Potresti fornire un codice di sistema con il framework di Google Test? – Joe

+2

Forse è possibile utilizzare i processi anziché i thread in modo che il simulatore di sistema non si lamenti. – Stephan

+0

Mi sono imbattuto nello stesso problema (SystemC lamentava che sc_start() veniva chiamato più di una volta). Nel caso tu sia interessato [qui] (http://stackoverflow.com/questions/25706294/running-boost-unit-tests-on-different-processes) puoi trovare la mia domanda originale e la soluzione che ho trovato . – betabandido

1

ho una seconda soluzione a questo problema che utilizza "cmake" e "CTest" (http://cmake.org/) . Il setup che ho usato crea un binario per ogni test. Ecco il file CMakeLists.txt ho usato:. File cxx

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) 

Ogni test _ * ha un metodo "sc_main" che esegue il test e il valore di ritorno indica se il test è stato superato o meno. Per eseguire i test semplicemente fare:

$ 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 

Se non si desidera eseguire il simulatore, si può semplicemente ignorare la chiamata a "sc_start" e chiudere l'applicazione dopo aver fatto tutto ciò che il test specifico che si desidera su un particolare modulo .

0

Molto spesso il Device-under-test (DUT) SystemC può essere ripristinato allo stato iniziale asserendo un segnale. Puoi utilizzare questo fatto e abilitare qualsiasi framework per test di unità C++ che desideri. Basta resettare DUT prima di eseguire ogni test, quindi non è necessario elaborarlo due volte.

Ecco un esempio con Google prova, e un semplice "accumulatore" in prova

  1. inizializzazione GTEST (:: :: test InitGoogleTest (& argc, argv);) da sc_main
  2. elaborare il proprio modello
  3. Esegui test da alcuni thread all'interno sc_module chiamando RUN_ALL_TESTS()
  4. In qualche modo sarà necessario passare il puntatore all'interfaccia SystemC DUT ai test. Ho usato variabile globale a tal fine

fonte:

#include <systemc.h> 
#include "gtest/gtest.h" 

class 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 (richiede l'installazione di 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) 
0

È necessario creare tutti i segnali necessari SystemC, Moduli SystemC e creare una connessione tra di essi prima di eseguire qualsiasi test in gtest. Ciò richiede di creare la propria implementazione gtest_main.cc. Naturalmente in SystemC è necessario inserire tutto nella funzione sc_main().

Per questo, vorrei utilizzare il modello di progettazione del Registro di sistema.

Per prima cosa creare la classe di registro (registro + factory + singleton). Questa classe sarà responsabile della memorizzazione dei costruttori registrati che utilizzano l'allocazione dinamica con un puntatore nuovo e intelligente nell'espressione lambda (vedere factory :: add class). Crea tutti gli oggetti usando il metodo factory :: create() prima di eseguire tutti i test. Quindi puoi ottenere oggetti usando il metodo factory :: get() nell'esecuzione del test.

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

creare la propria versione di implementazione gtest_main.cc. Chiama il metodo factory :: create() per creare tutti i segnali SystemC e i moduli SystemC prima di eseguire qualsiasi test RUN_ALL_TESTS(). Poiché la classe factory è un modello di progettazione singleton, chiamare il metodo factory :: destroy() dopo aver completato tutti i test per distruggere tutti gli oggetti SystemC creati.

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

Quindi definire dut classe nel test di creerà segnali SystemC e moduli SystemC. Nel costruttore fare la connessione tra i segnali e i moduli SystemC creati. Registro definito dut classe per registro oggetto utilizzando costruttore globale come questo factory :: add g. Dopo di che puoi ottenere il tuo oggetto dut usando il semplice metodo 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()); 
} 

Per più ispirazione, è possibile controllare il mio logica biblioteca per la verifica SystemC: https://github.com/tymonx/logic

Problemi correlati