Rational
This tutorial is aimed at Hardware and Software engineers wishing to acquire an understanding of the
SystemC language and its various uses.
No prior knowledge of C++ or HDL is required for the completion of this tutorial; however, throughout
this tutorial we will be drawing parallels between SystemC and other languages.
Note this is a deliberately simplified description of SystemC. Full details on the language,
coding styles; design guidelines can be found on the Esperan
SystemC Fundamentals class.
Introduction
SystemC is a C++ library used for supporting system level modeling. It supports various abstraction levels and can be used for fast, efficient designs and verification. The SystemC library is provided by the Open SystemC Initiative, a non-profit independent organisation. The OSCI is composed of numerous companies, universities and individuals. The ultimate aim of the organisation is to achieve IEEE standardisation for SystemC.
The SystemC reference simulator is freely available at www.systemc.org . Numerous EDA vendors provide commercial implementations of the SystemC language and support for mixed languages simulations. Along with the SystemC language, a number of additional application specific libraries are freely available either at www.systemc.org or www.testbuilder.net
Why SystemC instead of C++
The C++ language is based on sequential programming. Consequently it is not suited for the modeling of concurrent activities. Furthermore most system and hardware models require a notion of delays, clocks or time. features which are not present in C++ as a software programming language. As a result, complex and detailed systems cannot be easily described in C++ alone. Additionally communication mechanisms used in hardware models, such as signals and ports, are very different from those used in software programming. Lastly, the data types found in C++ are too remote from the actual hardware implementation. Ultimately, new dedicated data types and communication mechanisms have to be provided.
SystemC Implementation
The SystemC core language is based on C++. As a C++ library, layered on top of C++, SystemC defines data types dedicated to hardware modeling such as bit and vector types as well as fixed point types. Core language elements such as modules, processes, events, channels, event driven simulation kernel are also present. Finally, elementary channels such as signals or FIFOs are provided to implement communication mechanisms between concurrent objects.
Modules
A module is a C++ class - it encapsulates a hardware or software description. SystemC defines that any
module has to be derived from the existing class sc_module. SystemC modules are
analogous to Verilog modules or VHDL entity/architecture pairs, as they represent the basic building
block of a hierarchical system. By definition, modules communicate with other modules through channels and
via ports. Typically a module will contain numerous concurrent processes used to implement their
required behaviour. An example of a module is as follows:
class counter: public sc_module {
int value;
public:
sc_in clk;
sc_in count;
sc_in reset;
sc_out q;
SC_HAS_PROCESS(counter);
counter(sc_module_name nm): sc_module(nm), value(0) {
SC_METHOD(do_count);
sensitive<< clk.pos() << reset ;
}
protected:
void do_count() {
if (reset) { value = 0; }
else if (count) {
value++;
q.write(value);
}
}
};
This example illustrates the creation of a counter module. The first part consists in the declaration of
input and output ports. Ports are created form existing SystemC template classes:
sc_in<> and sc_out<>. A type passed as a template
argument defines the type of the data exchanged on the ports. The macro
SC_HAS_PROCESS is used to indicate to the simulator’s kernel that this
specific counter class will contain concurrent processes.
The constructor function is used to register any of the access functions as concurrent processes.
In this case, the do_count function is registered an
SC_METHOD which is similar to a Verilog always
or a VHDL process. It is worth observing that the constructor function defines
a parameter of type sc_module_name. This parameter is required by the
sc_module parent class. For the sake of simplicity, this parameter can be seen as a string type.
The last part of this module defines the do_count function. At this point,
it can be observed that the do_count function is being made sensitive to the
reset port and the positive edge of the clk port.
Consequently, it will only be activated on a change of either the reset or the clk ports.
Ports and Interfaces
As can be observed in the previous example, ports are defined as objects inside a module.
For obvious reasons they will have to be made publicly available to the outside world through the use of
the public keyword.
Although SystemC provides numerous predefined ports such as: sc_in<>,
sc_out<>, sc_inout<>,
sc_fifo_in<>, sc_fifo_out<>, and more, the language
also allows the definition of user defined ports and how should they be accessed. This is done through
the use of the sc_port<> class.
An example of the use of the sc_port is as follows:
sc_port< sc_signal_in_if, 1 > clk;
This declaration is, in effect, identical to:
sc_in clk ;
As it can be observed, ports are defined by a name clk,
a type bool and an interface sc_signal_in_if<>.
The interface sc_signal_in_if<> defines the restricted set of available messages
that can be send to the port clk. For instance the functions:
read() or default_event() are defined by the
sc_signal_in_if<> class. At this stage it worth pointing out that the
implementation of the functions defined inside an interface is provided by a third party element:
the channel. Channels will be discussed in greater detail at the end of this section.
Processes
Two kinds of process exist in SystemC: SC_METHOD and
SC_THREAD. To some extent, the two processes are similar since they will both
will contain sequential statements and have their own thread of execution; hence, they will operate
concurrently. Additionally, both SystemC processes allow for both static sensitivity and dynamic
sensitivity.
By definition, SC_METHOD cannot be suspended during its execution.
Once the execution of the SC_METHOD as been performed, it halts and waits
for new activities on its static or dynamic sensitivity list before executing again.
This is similar to a VHDL process or Verilog procedure with a sensitivity list.
In contrast with SC_METHOD, SC_THREAD can be
suspended during execution and resumed at a later stage. Furthermore, by definition,
SC_THREAD only executes once during simulation and then suspends.
This mechanism is akin to a Verilog initial block. For this reason, designers will commonly use an
infinite loop inside an SC_THREAD to prevent it from ever exiting before
the end of the simulation. An example of an SC_THREAD is as follows:
void do_count() {
while(true) {
if (reset) { value = 0; }
else if (count) {
value++;
q.write(value); }
wait();
}
}
Because of performance overheads, SC_METHOD implementations are usually
preferred to SC_THREAD.
Channels
Channels are SystemC’s communication medium. They can be seen as a more generalized form of signals.
SystemC provides a exhaustive range of predefined channels for generic uses such as:
sc_signal, sc_fifo, sc_semaphore etc.
Additionally SystemC permits the creation of user defined channels. This feature of the language
significantly differentiates it form other Hardware Description Languages such as Verilog or VHDL.
Providing abstraction at the interface level of components achieves greater design and modeling
flexibilities. By definition modules are interconnected via channels and ports.
In turn, ports and channels communicate via a common interface.
Note: For the purpose of this tutorial we will only consider predefined SystemC channels.
Composition
The principle of composition is analogous to hierarchical design. SystemC encourages composition for the
creation of complex systems. To illustrate composition, an additional component (testbench) is created.
A simple example of a test bench is as follows:
class testbench: public sc_module {
public:
sc_out clk;
sc_out reset;
sc_out count;
SC_HAS_PROCESS(testbench);
testbench(sc_module_name nm): sc_module(nm) {
SC_THREAD(clk_gen);
SC_THREAD(stimuli);
}
void clk_gen() {
while(true) {
clk.write(true);
wait(10, SC_NS);
clk.write(false);
wait(10, SC_NS);
}
}
void stimuli() {
while(true) {
reset.write(true);
count.write(false);
wait(10, SC_NS);
reset.write(false);
wait(50, SC_NS);
count.write(true);
wait(200, SC_NS);
}
}
};
This module defines two processes clk_gen and stimuli.
The clk_gen process is used to generate the clock signal
used by the counter.
The stimuli process is used to generate the enable
signal used by the counter. Both processes are registered as SC_THREAD since
they contain wait() statements.
The next step consists of instantiating a counter and a
testbench object inside a top level module as follows:-
class top: public sc_module {
public:
sc_signal clk_sig, count_sig, reset_sig;
sc_signal q_sig;
counter uut ; // Counter instance
testbench tb ; // Testbench instance
top(sc_module_name nm):
sc_module(nm), uut("uut"), tb("tb") {
// Testbench’s ports connection
tb.clk(clk_sig);
tb.reset(reset_sig);
tb.count(count_sig);
// Counter’s ports connection
uut.clk(clk_sig);
uut.reset(reset_sig);
uut.count(count_sig);
uut.q(q_sig);
}
};
This example illustrates composition as two objects are declared inside the class
top. SystemC defines that port/channel connections are performed inside the
constructor function. The semantics used for port/channel connection is:
instance_name.port_name(channel_name);
Simulation
Simulation instructions are usually located inside a function called: sc_main.
The sc_main functions are equivalent to the more conventional main function in C++.
This function will execute simulation specific commands such as setting the simulator’s resolution,
channels to be traced, top level instance, simulation running time and more.
An example of a sc_main function is as follows:-
int sc_main(int argc, char* argv[])
{
sc_set_time_resolution(1, SC_NS);
top verif_env("verif_env"); // Top level instance
// Creating a trace file
sc_trace_file *tf;
tf = sc_create_vcd_trace_file("trace");
sc_trace(tf, verif_env.clk_sig, "clk_sig");
sc_trace(tf, verif_env.reset_sig, "reset_sig");
sc_trace(tf, verif_env.count_sig, "count_sig");
sc_trace(tf, verif_env.q_sig, "q_sig");
// Running the simulation
sc_start(1000, SC_NS);
sc_close_vcd_trace_file(tf);
return (0);
}
In this example the top level component of class top is called:
verify_env. This declaration along with the sc_start()
function call are sufficient to run a SystemC simulation.
However a number of trace commands have been added to allow visualization of the results of the simulation.
These traces files are stored using the VCD format inside a file called: trace.vcd.
Conclusions
This section presented the creation of SystemC modules and their associated concurrent processes.
We examined the differences between SC_METHOD and
SC_THREAD and illustrated the composition mechanism in SystemC.
Lastly we saw how the sc_main function is used to provide a simulation “layer”.
The next tutorial considers the simulation of a simple worked example.
Back to top