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���|7V ���|7V �|7V ��|7V 8��|7V ���|7V S0 ���|7V > 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
[email protected]
|