HOME

    Electronics Directory Articles/ Tutorials eBooks

About Us

FORUM Links Contact Us
   

SystemC: An Introduction for beginners

By Mahesh Prasad N

 

Here is an tutorial on systemC for those who want to have a quick start. This is not completed yet. So, Send us suggestions in continuing this article. please keep visiting our website to read the updated versions of this tutorial.
Why systemC?

          If you are reading this article it is just because you are aware of the current trend in the field of hardware design. That is to say that systemC is replacing the specially designed HDL's like Verilog and VHDL in many situations. This does not mean that these HDL's are obsolete now, instead, systemC supports a new approach to design a system.

          The systemC born because of the necessities of the current electronic industry: Electronic gadgets are incorporating greater and greater functionality  today, but not compromising with the time to produce and market the gadgets. For example, you want your mobile handset to have internet facility but you are not ready to wait for one year for that facility to come. It is easy for you to demand, but it is not so easy for electronic design engineers who design the system. The greater complexity of the future systems are making the situation still worst. Previously, the C (or C++)  was used to write the software part of the design. For hardware part any of the existing HDL's were used to design the hardware. It was very difficult to setup a testbench which is common for both, since they are entirely different languages. The introduction of systemC solved many of these problems.

          The systemC is nothing but a C++ class library specially designed for system design. This is an open source ware, maintained by OSCI (Open source SystemC Initiative). (visit http://systemc.org for more details.)

The advantages of using systemC are:

         1. It inherits all the features of C++, which is a stable programming language accepted all over the world. It has got large language constructs, which makes easier to write the program with less efforts.

         2. Rich in data types: along with the types supported by C++, systemC supports the use of special data types which are often used by the hardware engineers.

         3. It comes with a strong simulation kernel to enable the designers to write good test benches easily, and to simulate it. This is so important because the functional verification at the system level saves a lot of money and time.

         4. It introduces the notion of time to C++, to simulate synchronous hardware designs. This is common in most of the HDL's.

         5. While most of the HDL's support the RTL level of design, systemC supports the design at an higher abstraction level. This enables large systems to be modeled easily without worrying the implementation of it. It also supports RTL design, and this subset is usually called as systemC RTL.

         6. Concurrency: To simulate the concurrent behavior of the digital hardware, the simulation kernel is is so designed that all the 'processes' are executed concurrently, irrespective of the order in which they are called.

The following diagram shows the various design stages and the supported stages by HDL's and systemC: 

How to start with systemC?

         First of all, you have to have a C++ compiler either for WINDOWS or LINUX. Currently, systemC package supports VC++ compiler version 7 or more, and the native GCC compiler of LINUX. For more details about the installation and supported environments, refer the corresponding documents. The systemC itself can be downloaded from the OSCI website (http://systemc.org ) for free of charge.

Prerequisites:

                         1. You should be an experienced C++ programmer.
                         2. The basic knowledge of the common digital circuits both synchronous and asynchronous.
                         3. Prior knowledge of any of the HDL's is a bonus.

SystemC program structure:

Every design consists of a class or module 'SC_MODULE' of name 'module name' as given below:

SC_MODULE(<module name>)

{..........

............//this part is described next

...........

};

Every module should have a constructor 'SC_CTOR' with the same name as that of the module. The above module with the constructor looks like :

SC_MODULE(<module name>)
{..........
   ............//these steps are described next
   SC_CTOR(<module name>)
   {...........
   ............//these steps are described next
   }
};

The ports are used to communicate with the external modules or channels. These are defined in the beginning of the module. The direction of the port is specified using 'sc_in' and 'sc_out' qualifiers. The allowed datatypes are to be discussed later.

Every module should have at least one process or method which gives the functionality of that particular module. The method declaration should follow the port declaration. The process definition can be either within the module (inline) or outside the module:

SC_MODULE(<module name>)
{
sc_in<datatype> port1; sc_out<datatype> port2; ............//can have any number of ports
<return type> <process name> (<argument type>); ............//there may be more than one //processes
..........//any other codes
SC_CTOR(<module name>)
{
SC_METHOD(<process name>);//immediate call to the process 'process name'
sensitive << <sensitivity list>;
...........//any other code
}
};

Note that the constructor itself calls the process which means that all the processes are executed when ever an object of the module is instantiated or the value of any of the signal or port in the sensitivity list changes..

Also note that all the processes are methods but all methods are not processes. The process is always called from the constructor, and usually associated with a sensitivity list. The syntax for specifying the sensitivity list is: sensitivity << <sensitivity list>, which uses overloaded operator '<<'. More over, all the processes are executed concurrently, where as methods are executed sequentially.

My first code:

Theory without practical experience is of no use. So we shall study more with an example of OR gate:

Here is the program for that:

//file: or_gate.h
//this is the 'DUT'
#include "systemc.h"

SC_MODULE(or_gate)
{
sc_in<sc_bit> a;
sc_in<sc_bit> b;
sc_out<sc_bit> c;

void prc_or_gate() {c=a | b;}//this is the process which runs
//in concurrent with all other processes

SC_CTOR(or_gate)
{
SC_METHOD(prc_or_gate);
sensitive << a << b;
}
};

The above program could also be written in a standard form by putting the process definition in a separate file named or_gate.cpp. But since this file would be very small in the present example, it is not done.

Writing a testbench:

The design of OR gate is a simple code as given above. But that's not over. How you can assure that the above design is working correctly?. Here the concept of testbench comes. Whenever you design a design, you have to properly setup a testbench to verify the functionality of your design as given below:
 

In the above diagram, there are three blocks: Design Under Test (DUT), driver and monitor. Each block is a separate piece of code, which are given later in this article.

The DUT is your actual design to be tested. The driver program generates stimuli are then given to the DUT as test inputs (this is similar to test vectors in ABEL). The monitor program does the job of monitoring. That is, it accepts the output (response)of the DUT, and displays it either to a standard console or to a text file. By carefully looking at this output and comparing with the expected output for the given stimuli, we can infer whether the design is working properly or not. All the three modules are written as given above with .h and .cpp files for each module (you are free to choose any format of files, but this is the standard method of maintaining the codes. This format makes debugging quite easier.)

But again, this setup is also incomplete, since we do not have the main program which instantiates all these modules. There is a well defined structure for the main function which is given below:

//file: driver.h
// this is the driver program for the OR gate
#include "systemc.h"

SC_MODULE(driver)
{
sc_out<sc_bit> d_a;
sc_out<sc_bit> d_b; //inputs to the OR gate are of type sc_bit

void drivea();
void driveb();// these are two processes to stimulate the OR gate

SC_CTOR(driver)
{
SC_THREAD (drivea);
SC_THREAD (driveb);//processes are called here
}
};

 

 

//file: driver.cpp
// this file contains the process definitions
#include "driver.h"

void driver :: drivea()
{
d_a.write((sc_bit)false);//(ba)=00
wait(5,SC_NS);
d_a.write((sc_bit)true);//(ba)=01
wait(5, SC_NS);
d_a.write((sc_bit)0);//(ba)=10, false=0
wait(5,SC_NS);
d_a.write((sc_bit)1);//(ba)=11, true=1
wait(5,SC_NS);
}

void driver :: driveb()
{
d_b.write((sc_bit)0);
wait(10,SC_NS);
d_b.write((sc_bit)1);
wait(5,SC_NS);
}


             In the above code, everything would clear except the wait() statement. If you are familiar with VHDL, this is the same as the wait statement in VHDL also. wait suspends the execution of the process until specified time elapses. For ex: wait(10,SC_NS) suspends execution for 10 ns. wait() on the other hand, suspends the execution indefinitely, until any of the signals in its sensitivity list triggers it.

//file: monitor.h
//this the monitor module
#include "systemc.h"

SC_MODULE(monitor)
{
sc_in<sc_bit> m_a, m_b;
sc_in<sc_bit> m_c;//both input and output of the OR module are to
//be monitored
void prc_monitor()
{
cout <<" At "<<sc_simulation_time()<<" input is : ";
cout <<m_a<<" , "<<m_b<<" output is : "<<m_c<<endl;
}

SC_CTOR(monitor)
{

SC_METHOD(prc_monitor);
sensitive << m_a <<m_b <<m_c;
//whenever the i/p to the gate changes,
//or the o/p changes, the process prc_monitor triggers
}
};

 


Here also, the file monitor.cpp is not shown separately, since this file would be too small

 

//file: main_sc.cpp
//this is the main program which is used to
//instantiate all the modules and to bind them
//this also starts the simulation

#include "driver.h"
#include "monitor.h"
#include "or_gate.h" //all the header files should be included here.

int sc_main(int argc , char *argv[] )
{
sc_signal <sc_bit> t_a, t_b, t_c;
//signals t_a, t_b, t_c are used to connec all the modules

or_gate g1("orgate");
driver d1("driver");
monitor m1("monitor");

g1.a(t_a); g1.b(t_b); g1.c(t_c);
//the above is called named binding

d1<<t_a<<t_b;
m1<<t_a<<t_b<<t_c; //these are called positional binding

sc_start(100,SC_NS);//start the simulation and run for 100 ns

return 0;//return success
}



       On compiling and running the above program, the output at the console would be something like this:

At 0 s  input is: 0 , 0 output is: 0
At 5 ns  input is: 0 , 1 output is: 0
At 5 ns  input is: 0 , 1 output is: 1
At 10 ns  input is: 1 , 0 output is: 1
At 15 ns  input is: 1 , 1 output is: 1

If you are new to this kind of exercises, you should wonder why the output is 0 in the second line. Somebody will also start arguing that the design is not functioning as an OR gate. But it is an OR gate. The answer for this lies in the concept of delta delay which is a very common word for hardware designers. The details of this will be covered later, but for the time being, remember that the output of any expression is assigned after an infinitesimal amount of delay called delta delay. This kind of delay is needed to model any hardware because of the inherent property of the digital hardware that is the propagation delay.  Also note that  we have used sc_main() instead of main(), which are same of course, but the former will help in debugging.  

 .............. to be continued later..

To continue with, we need your suggestions. Please send your valuable feedback to mahesh@electrosofts.com

 

 

Home   |    About Us   |   Articles/ Tutorials   |   Downloads   |   Feedback   |   Links   |   eBooks   |   Privacy Policy
Copyright 2005-2007 electroSofts.com.
webmaster@electroSofts.com