Free Online Training Events
Free Technical Resources
This must be the most often asked question on the SystemC Forums! The basic idea is to create a signal, but using a user defined data type. For instance, I might create a structure, and then use that structure within a signal.
A careful reading of the release notes for SystemC reveals that you must create versions of operator =, operator <<, and operator == . Also, if you need to trace your signal, you need to provide an overloaded sc_trace function.
The following piece of code shows a complex data type and the appropriate functions. This has all been defined in one place (the class MyType) for convenience, i.e. all the definitions are kept together.
#ifndef MYTYPE_H #define MYTYPE_H #include "systemc.h" #include #include class MyType { private: unsigned info; bool flag; public: // constructor MyType (unsigned info_ = 0, bool flag_ = false) { info = info_; flag = flag_; } inline bool operator == (const MyType & rhs) const { return (rhs.info == info && rhs.flag == flag ); } inline MyType& operator = (const MyType& rhs) { info = rhs.info; flag = rhs.flag; return *this; } inline friend void sc_trace(sc_trace_file *tf, const MyType & v, const std::string & NAME ) { sc_trace(tf,v.info, NAME + ".info"); sc_trace(tf,v.flag, NAME + ".flag"); } inline friend ostream& operator << ( ostream& os, MyType const & v ) { os << "(" << v.info << "," << std::boolalpha << v.flag << ")"; return os; } }; #endif
We've made a complete example you can download. The files are in UNIX format here, and in DOS format here.
Sometimes people want to make a parameterized module. There are two basic ways to do this:
The easiest to understand is the first. It's useful for parameterizing modules which need e.g. a different amount of dynamic memory when the module is declared. Another common use is to switch on debugging information. By using default values, it's possible to design the module so that some of the parameters may be left out if not needed.
Here is a simple parameterized RAM model. The key is the use of a normal constructor together with the SC_HAS_PROCESS macro, instead of using SC_CTOR.
#ifndef RAM_H #define RAM_H #include "systemc.h" SC_MODULE(ram) { sc_in<bool> clock; sc_in<bool> RnW; // ReadNotWrite sc_in<int> address; sc_inout<int> data; void ram_proc(); SC_HAS_PROCESS(ram); ram(sc_module_name name_, int size_=64, bool debug_ = false) : sc_module(name_), size(size_), debug(debug_) { SC_THREAD(ram_proc); sensitive << clock.pos(); buffer = new int[size]; if (debug) { cout << "Running constructor of " << name() << endl; } } private: int * buffer; const int size; const bool debug; }; void ram::ram_proc() { while(true) { wait(); // synchronous to rising edge if (RnW) { data = buffer[address]; } else { buffer[address] = data; } } } #endif
Just copy and paste the code above into your C++ compiler and try compiling it! You'll have to write a testbench to test the ram fully. In the testbench, e.g. in sc_main, you can instance the ram:
ram r1("R1"); // 64 locations, debug off ram r2("R2", 100); // 100 locations, debug off ram r3("R3", 64, true); // 64 locations, debug on
This example has a number of interesting points:
In this question, we look at making a simple RAM model using a template class. This is potentially more flexible than using constructor parameters, as the template class may be parameterized by type, as well as by value. For instance, we could create a RAM model that could store a particular type of data, as well as specifying the size of the RAM.
Here then is a simple parameterized RAM model where we can set both the data type stored in the RAM and the number of elements (the size).
It is usual that functions that are part of a template class have to be defined in the header file where the template class is created. This is a limitation of current compilers.
#ifndef RAMT_H #define RAMT_H #include "systemc.h" template <class T, int size = 100> SC_MODULE(ram) { sc_in<bool> clock; sc_in<bool> RnW; // ReadNotWrite sc_in<int> address; sc_inout<T> data; void ram_proc(); SC_HAS_PROCESS(ram); ram(sc_module_name name_, bool debug_ = false) : sc_module(name_), debug(debug_) { SC_THREAD(ram_proc); sensitive << clock.pos(); buffer = new T[size]; if (debug) { cout << "Running constructor of " << name() << endl; cout << "Number of locations is " << size << endl; } } private: T * buffer; const bool debug; }; template <class T, int size> void ram<T,size>::ram_proc() { while(true) { wait(); // synchronous to rising edge if (RnW) { data = buffer[address]; } else { buffer[address] = data; } } } #endif
Just copy and paste the code above into your C++ compiler and try compiling it! You'll have to write a testbench to test the ram fully. In the testbench, e.g. in sc_main, you can instance the ram:
ram<int, 16> r1("R1", true); // int, 16 locations, debug on ram<float, 16> r2("R2", false); // float, 16 locations, debug off ram<double> r3("R3", true); // double, 100 locations, debug on
Note the following...