Global training solutions for engineers creating the world's electronics

SystemC FAQ

  1. sc_signal with user defined data types
  2. Parameterized modules using constructor parameters
  3. Parameterized modules using templates

1. sc_signal with user defined data types

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.

back to the top

2. Parameterized modules using constructor parameters

Sometimes people want to make a parameterized module. There are two basic ways to do this:

  • Use a constructor parameter
  • Use a template class

 

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:

  • Note the use of sc_module(name_) to pass the name of the module to the base class. In fact this is optional due to the way SystemC works, but we recommend including it for clarity.
  • The combination of SC_HAS_PROCESS and a normal constructor replaces the use of SC_CTOR
  • SC_HAS_PROCESS is only needed if you have SC_THREADs or SC_METHODs in your module! If there are no processes then you don't need SC_HAS_PROCESS

back to the top

3. Parameterized modules using templates

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

  • The member functions must be declared in the header file - they can't be put in a separate .cpp file with current C++ compilers
  • It is possible to have a default value on template parameters - note how size is given a default value of 100

 

 

back to the top

Great training!! Excellent Instructor, Excellent facility ...Met all my expectations.
Henry Hastings
Lockheed Martin

View more references