You are on page 1of 46

Modules and Processes in SystemC

A Presentation by: Najmeh Fakhraie Mozhgan Nazarian-Naeini

Hardware-Software Codesign Spring 2006

The Nightmares of Citation

References:
An Introduction to SystemC 1.0 : Bernard Niemann, Fraunhoffer Institute for Integrated Circuits SystemC Tutorial: John Moondanos, Strategic CAD labs, Intel Corp. GSRC Visiting Fellow, UC Berkeley SystemC 2.0.1 Language Reference Manual SystemC Tutorial: Anonymous Author Compositional Approach to SystemC Design : Semantics of SystemC : R.K Shyamasundar, IBM Research Lab Doulos SystemC Tutorial The NEW YORKER Cartoon Bank

Modules in SystemC
A module is the basic structural building block in SystemC. Modules may contain:
Ports Data Members Channel Processes Member functions not registered as processes Instances of other modules

A new type of module is created by deriving from the container class sc_module
Publicly deriving from class sc_module. Or alternatively, a module can be created with use of the SC_MODULE macro.

Modules in SystemC
An example for the former would be:
class my_module : public sc_module { .... };

And an example for the latter:


SC_MODULE(my_module) { .... };

Module Constructor
Every module can have a module constructor Accepts one argument which is the module name Will be thoroughly explained in the upcoming sections

Modules Instantiation
Instantiation develops hierarchy through out the design

Declaration
SC_MODULE(ex2) { . . . ex1 ex1_instance; SC_CTOR(ex2) : ex1_instance(ex1_anyname) { //body } };

Modules Instantiation
Pointer
SC_MODULE(ex2) { . . . ex1 *ex1_instance; SC_CTOR(ex2) { ex1_instance = new ex1(ex1_anyname); //body } };

Module Destructor
SC_MODULE(ex2) { ~ex2() { delete ex1_instance; } };

Processes in SystemC
An argument that begs the question: Processes are small pieces of code that run concurrently with other processes. All high-level system level design (SLD) tools use an underlying model of a network of processes. Functionality is described in processes. Processes must be contained within a module. They are registered as processes with the SystemC kernel, using a process declaration in the module constructor. Processes accept no arguments and produce no output

XOR Using Four NAND Gates

Step One: Modeling the NAND Gate


A NAND Gate is a combination circuit. Its output is purely a function of its input. Use the simplest kind of SystemC process to model it: SC_METHOD. SC_METHODs are simply C++ functions. Therefore, the SystemC class library must make them behave like processes, in particular:
The SystemC class library contains a simulation kernel a piece of code that models the passing of time, and calls functions to calculate their outputs whenever their inputs change. The function must be declared an SC_METHOD and made sensitive to its inputs.

NAND Gate
#include "systemc.h" SC_MODULE(nand2) //declare nand2 sc_module { sc_in<bool> A, B; //input signal ports sc_out<bool> F; //output signal ports void do_nand2() //a C++ function { F.write( !(A.read() && B.read()) ); } SC_CTOR(nand2) { SC_METHOD(do_nand2); //register do_nand2 with kernel sensitive << A << B: //sensitivity list } };

What just happened?


SC_MODULE: Hierarchy in SystemC is created by using a class sc_module. sc_module may be used directly, or may be hidden using the macro SC_MODULE. The example SC_MODULE above creates an sc_module class object called nand2. Ports: SystemC numerous predefined ports such as sc_in<>, sc_out<>, sc_inout<>. The language also allows the definition of user defined ports through the use of the sc_port<> class.

sc_port<sc_signal_in_if<bool>, 1> clk;

The NAND Function


do_nand2: The function that does the work is declared. The input and output ports include methods read( ) and write( ) to allow reading and writing to the ports. A and B are read, the NAND function is calculated and the result is written to F using the write( ) method. We could get away without writing the read( ) and write( ) methods by using operators: F = !( A && B);

Constructor for sc_module Instance nand2


SystemC provides a shorthand way of writing the constructor using a macro SC_CTOR. This constructor does the following:
Create hierarchy (none in this case) Register functions as processes with the simulation kernel Declare sensitivity list for processes Accepts one argument only, which is the module name In this example, the constructor declares that do_nand2 is an SC_METHOD, and says that any event on ports A and B must make the kernel run the function (calculate a new value for F).

XOR Gate
#include "systemc.h" #inclue "nand2.h" SC_MODULE(xor2) { sc_in<bool> A, B; sc_out<bool> F; nand2 n1, n2, n3, n4; sc_signal<bool> S1, S2, S3; SC_CTOR(xor2) : n1("N1"), n2("N2"), n3("N3"), n4("N4") { n1.A(A); n1.B(B); n1.F(S1);

XOR Gate (continued)


n2 << A << S1 << S2; n3(S1); n3(B); n3(S3); n4.A(S2); n4.B(S3); n4.F(F); } };

What just Happened II?


Header Files: Note the inclusion of nand2.h. This allows access to the module containing the NAND gate. Ports: It is permitted to reuse the names A, B and F as this a different level of the hierarchy. Declaring Intermediate Wires: They are declared using sc_signal. sc_signal is a class with a template parameter specifying the type of data the signal can hold bool in this example. It is a primitive, built-in channel with the SystemC library.

XOR Constructor
Must have four instances of nand2. After the port declarations, they are declared. And a label must be declared for each instance. The four labels, N1, N2, N3 and N4 are passed to the constructors of the instances of nand2 by using an initializer list on the constructor of xor2. Lastly, the ports are wired up. This can be done using parentheses () or << and >>.
Using << and >> only allows the ports and signals to be listed by position (n2). Using () allows either by position (n3) or by name (n1 and n4).

More on Modules
They map functionality of HW/SW blocks The basic building block of every system Can contain a whole hierarchy of sub-modules and provide private variable/signals. Modules can interface to each other via ports/interfaces/channels Module functionality is achieved by means of processes

Module Hierarchy

Ports, Interfaces, Channels


Data transmitted and held via channels Interfaces provide the operations that the channel provides Ports standardize interaction of modules with the external world At elaboration time, ports are BOUNDED to channels via interfaces

Interfaces
An interface consists of a set of operations (their prototype), not of their implementation which is a burden on the channels. There are two basic interfaces derived from sc_interface class: sc_signal_in_if<T> provides a virtual functional read( ) returning a reference to T sc_signal_inout_if<T> provides a virtual functional write( ) taking a reference to T an out interface is identical to an inout interface

Ports
They provide communication functions to modules Derived from SystemC class sc_port<> On the outside, they connect to channels by means of interfaces Typical channel (in RTL mode): sc_signal Specialized classes were derived: sc_in<class T>, sc_out<class T>, sc_inout<classT>, which are ports connected to N = 1 interfaces of type sc_signal_in_if<class T> 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 this class.

Channels
SystemCs communication medium This feature of SystemC differentiates it from other HDLs such as Verilog Provides abstraction at the interface level of components and thus achieves greater design modeling flexibilities By definition, modules are interconnected via channels and ports. In turn, channels and ports communication via a common interface Implementation of interfaces functions

Channels
Communication among modules and among processes within a module Any channel must:
be derived from sc_channel class be derived from one or more classes derived from sc_interface provide implementations for all pure virtual functions defined in its parents interfaces

Fifo Example

FIFO: Declaration of Interfaces


class write_if : public sc_interface { public: virtual void write(char) = 0; virtual void reset() = 0; }; class read_if : public sc_interface { public: virtual void read(char&) = 0; virtual int num_available() = 0; };

FIFO: Declaration of Channel


class fifo: public sc_channel, public write_if, public read_if { private: int max_elements=10; char data[max_elements]; int num_elements, first; sc_event write_event, read_event; bool fifo_empty() {}; bool fifo_full() {}; public: fifo() : num_elements(0), first(0); void write(char c) { if (fifo_full()) wait(read_event); data[ <you calculate> ] = c; ++num_elements; write_event.notify(); } void read(char &c) { if (fifo_empty()) wait(write_event); c = data[first]; --num_elements; first = ...; read_event.notify(); }

FIFO: Declaration of Channel


void reset() { num_elements = first = 0; } int num_available() { return num_elements; } }; // end of class declarations

FIFO: Producer & Consumer


SC_MODULE(producer) { public: sc_port<write_if> out; SC_CTOR(producer) { SC_THREAD(main); } void main() { char c; while (true) { out.write(c); if() out.reset(); } } }; } void main() { char c; while (true) { in.read(c); cout<< in.num_available(); } } }; { SC_THREAD(main); SC_MODULE(consumer) { public: sc_port<read_if> in; SC_CTOR(consumer)

FIFO Completed
SC_MODULE(top) { public: fifo afifo; producer *pproducer; consumer *pconsumer; SC_CTOR(top) { pproducer=new producer(Producer); pproducer->out(afifo); pconsumer=new consumer(Consumer); pconsumer->in(afifo); } };

More on Processes
They provide module functionality Implemented as member functions of the module and declared to be SystemC process in SC_CTOR Three kinds of process available:
SC_METHOD SC_THREAD SC_CTHREAD

These macros are needed because a member function is not necessarily a process. The macros operate specifics registrations with the scheduler All these processes in the design run concurrently Code inside of each process is sequential

SC_METHOD
Sensitive to any changes on input ports (i.e., it is sensitive to a set of signals, on its sensitivity list. It can be sensitive to any change on a signal e.g., the positive or negative edge of a Boolean signal). Usually used to model purely combinational logic (i.e., NORs, NANDs, MUX) Cannot be suspended. All the function code is executed every time the SC_METHOD is invoked (whenever any of the inputs it is sensitive to change). Instructions are executed in order. Does not remember internal state among invocations (unless explicitly kept in member variables)

Defining the Sensitivity List of a Process


sensitive with the ( ) operator
Takes a single port or signal as argument sensitive(sig1); sensitive(sig2); sensitive(sig3);

sensitive with the stream notation


Takes an arbitrary number of arguments sensitive << sig1 << sig2 << sig3;

sensitive_pos with either ( ) or << operator


Defines sensitivity to positive edge of Boolean signal or clock sensitive_pos << clk;

sensitive_neg with either ( ) or << operator


Defines sensitivity to negative edge of Boolean signal or clock sensitive_neg << clk;

SC_THREAD
Still has a sensitivity list and may be sensitive to any change on a signal. It is reactivated whenever any of the inputs it is sensitive to changes. Once it is reactivated:
Instructions are executed infinitely fast (in terms of internal simulation time) until the next occurrence of a wait( ) statement. Instructions are executed in order. The next time the process is reactivated, the execution will continue after the wait( ) statement.

wait( ) suspends execution of the process until the process is invoked again

SC_THREAD
Akin to a Verilog initial block Executes only once during simulation and then suspends Designers commonly use an infinite loop inside an SC_THREAD to prevent it from ever exiting before the end of the simulation Adds the ability to be suspended to SC_METHOD processes by means of wait() calls (and derivatives) Remembers its internal state among invocations (i.e., execution resumes from where it was left) Very useful for clocked systems, memory elements, multi-cycle behavior

An Example of SC_THREAD
void do_count() { while(true) { if(reset) { value = 0; } else if (count) { value++; q.write(value); } wait(); } }

Thread Processes: wait( ) Function


wait( ) may be used in both SC_THREAD and SC_CTHREAD processes but not in SC_METHOD process block wait( ) suspends execution of the process until the process is invoked again wait(<pos_int>) may be used to wait for a certain number of cycles (SC_CTHREAD only) In Synchronous process (SC_CTHREAD)
Statements before the wait( ) are executed in one cycle Statements after the wait( ) executed in the next cycle

In Asynchronous process (SC_THREAD)


Statements before the wait( ) are executed in the last event Statements after the wait( ) are executed in the next event

Another Example
SC_MODULE(my_module) { sc_in<bool> id; sc_in<bool> clock; sc_in<sc_uint<3> > in_a; sc_in<sc_uint<3> > in_b; sc_out<sc_uint<3> > out_c; void my_thread(); SC_CTOR(my_module) { SC_THREAD(my_thread); sensitive << clock.pos(); } }; //my_module.cpp void my_module:: my_thread() { while(true) { if (id.read()) out_c.write(in_a.read()); else out_c.write(in_b.read()); wait(); } };

SC_CTHREAD
Will be deprecated in future releases Almost identical to SC_THREAD, but implements clocked threads Sensitive only to one edge of one and only one clock It is not triggered if inputs other than the clock change Models the behavior of unregistered inputs and registered outputs Useful for high level simulations, where the clock is used as the only synchronization device Adds wait_until( ) and watching( ) semantics for easy deployment

SC_CTHREAD
Once invoked: The statements execute until a wait( ) statement is encountered. At the wait( ) statement, the process execution is suspended At the next execution, process execution starts from the statement after the wait( ) statement Local variables defined in the process function are saved each time the process is suspended

Counter in SystemC

Counter in SystemC
SC_MODULE(countsub) { sc_in<double> in1; sc_in<double> in2; sc_out<double> sum; sc_out<double> diff; sc_in<bool> clk; void addsub(); //Constructor: SC_CTOR(addsub) { //Declare addsub as SC_METHOD SC_METHOD(addsub); dont_intialize(); //make it sensitive to positive clock sensitive_pos << clk; } }; //Definition of addsub method void countsub::addsub() { double a; double b; a = in1.read(); b = in2.read(); sum.write(a+b); diff.write(a-b); };

The End

You might also like