2011-02-07 11 views
6

Estoy trabajando en un proyecto en SystemC y quiero incorporar pruebas unitarias. ¿Es posible utilizar marcos de prueba de unidades existentes con SystemC?Uso de marcos de prueba de unidades existentes con SystemC

Lo pregunto porque parece que los módulos SystemC solo se ejecutan con el kernel de simulación, y quiero usar pruebas unitarias en los módulos mismos.

Respuesta

2

Pude ejecutar 2 pruebas de SystemC utilizando la llamada al sistema de horquillas. Usé el ejemplo tutorial en doulos.com y el marco Google Test. Pude ejecutar la prueba dos veces, pero recibí un error impreso por el simulador SystemC sobre cómo comenzar la prueba después de llamar a sc_stop. Sin embargo, independientemente del error, el simulador funciona bien la segunda vez.

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. 

ACTUALIZACIÓN: Ejemplo de código conforme a lo solicitado:

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

Gracias por la respuesta! ¿Podría proporcionar algún código systemC con el marco de Google Test? – Joe

+2

Quizás pueda usar procesos en lugar de hilos para que el simulador systemc no se queje. – Stephan

+0

Me encontré con el mismo problema (SystemC quejándose de que sc_start() ha sido llamado más de una vez). En caso de que esté interesado [aquí] (http://stackoverflow.com/questions/25706294/running-boost-unit-tests-on-different-processes) puede encontrar mi pregunta original y la solución que se me ocurrió . – betabandido

1

que tiene una segunda solución a esta pregunta que utiliza "cmake" y "ctest" (http://cmake.org/) . La configuración que utilicé crea un binario para cada prueba. Aquí está el archivo CMakeLists.txt he utilizado:. CXX archivo

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) 

Cada prueba _ * tiene un método "sc_main" que ejecuta la prueba y el valor de retorno indica si la prueba correcta o incorrecta. Para ejecutar las pruebas sólo tiene que hacer:

$ 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 

Si no desea ejecutar el simulador, sólo tiene que saltar a la llamada "sc_start" y salir de la aplicación después de hacer cualquier prueba específica que desea en un módulo en particular .

0

Muy a menudo, el dispositivo bajo prueba (DUT) del sistema C puede restablecerse al estado inicial al confirmar alguna señal. Puede utilizar este hecho y habilitar cualquier marco de prueba de unidad de C++ que desee. Simplemente reinicie DUT antes de ejecutar cada prueba, por lo que no necesita elaborarlo dos veces.

Aquí se muestra un ejemplo con Google prueba, y un simple "acumulador" DUT

  1. Inicializar GTEST (prueba :: :: InitGoogleTest (& argc, argv);) desde sc_main
  2. elaborado su modelo
  3. pruebas ejecutadas mediante un hilo en el interior sc_module por RUN_ALL_TESTS llamada()
  4. Usted necesitará alguna manera de pasar el puntero a la interfaz SystemC DUT a sus pruebas. He usado variable global para ese propósito

fuente:

#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 (Requiere la instalación 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

Debe crear todas las señales necesarias SystemC, Módulos SystemC y hacer conexión entre ellos antes de ejecutar cualquier prueba en gtest. Esto requiere crear una implementación propia de gtest_main.cc. Naturalmente, en SystemC debe poner todo en la función sc_main().

Para esto, utilizaría el patrón de diseño de registro.

Primero cree la clase de registro (registro + fábrica + singleton). Esta clase será responsable de almacenar los constructores registrados utilizando la asignación dinámica con un puntero nuevo e inteligente en la expresión lambda (consulte fábrica :: agregar la clase). Cree todos los objetos usando fábrica :: crear() método antes de ejecutar todas las pruebas. Luego puede obtener el objeto usando el método fábrica :: get() en su ejecución de prueba.

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

crear su propia versión de la aplicación gtest_main.cc. Llame al fábrica :: crear() método para crear todas las señales SystemC y módulos SystemC antes de ejecutar cualquier prueba RUN_ALL_TESTS(). Debido a que la clase de fábrica es un patrón de diseño singleton, llame al método fábrica :: destroy() después de terminar todas las pruebas para destruir todos los objetos SystemC creados.

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

A continuación, defina dut clase en su ensayo que va a crear señales SystemC y módulos SystemC. En constructor hacer la conexión entre las señales y los módulos SystemC creados. Registro definido dut class to registry object using global constructor like this factory :: add g. Después de lo que puede obtener su objeto dut usando el método simple 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()); 
} 

Para más inspiración, se puede comprobar mi lógica biblioteca para la verificación SystemC: https://github.com/tymonx/logic

Cuestiones relacionadas