Beruflich Dokumente
Kultur Dokumente
1. Introduction to SystemC
SystemC library has been developed to simulate architectures. It is a library of C++ classes,
global functions, data types and a simulation kernel. By using C/C++ development tools and
the SystemC library, executable models can be created in order to simulate, validate, and
optimize the system being designed. The executable model is essentially a C++ program
that exhibits the same behavior as the system when executed.
2. Getting Started
When you start to read a programming language book, it starts with a hello world
example program to understand the language. Once you understand the basic program, you
can do something more interesting in that language. We take the same approach and write a
hello world example in SystemC.
We need to install SystemC, before we can actually write a program. SystemC installation is
sometimes horrible, therefore we decided to provide you a precompiled binary of
SystemC 2.2 which can execute on Mac OS X (PPC and x86), Linux(32 & 64 bit x86) and
Solaris (sun4u). You can copy the given directory to your machine and can execute
SystemC programs.
/home/jianfu/systemc
If you want to install SystemC yourself on your machine, you are referred to
(http://www.systemc.org) to download the source code and compile it.
We also provide the framework for this lab course, which has a Makefile that is configured
to use the above SystemC installation. It also has some helper functions to read addresses
from the provide trace files and print the required results in the suggested format. You can
copy the given directory to your machine.
/home/jianfu/aca2011
Now we have the SystemC installed and we have the Makefile to compile a C++ program
using SystemC library. Lets first compile the hello_world example and see the output.
While you are in aca2011 directory, type make hello_world. This will compile the
program to generate the binary which you can then execute by typing ./hello_world.bin.
See the output given below.
$ make hello_world
$ ./hello_world.bin
SystemC 2.2.0 --- Nov 24 2009 16:53:40
Copyright (c) 1996-2006 by all Contributors
ALL RIGHTS RESERVED
Hello World.
Congratulations! You successfully execute your first program in SystemC. You might want
to have a look at Makefile to see how to compile a C++ program with SystemC library. The
hello_world program is given below.
// All systemc modules should include systemc.h header file
#include "systemc.h"
// Hello_world is module name
SC_MODULE (hello_world)
{
SC_CTOR (hello_world)
{
// Nothing in constructor
}
void say_hello()
{
//Print "Hello World" to the console.
cout << "Hello World.\n";
}
};
// sc_main in top level function like in C++ main
int sc_main(int argc, char* argv[])
{
hello_world hello("HELLO");
// Print the hello world
hello.say_hello();
return(0);
}
It is very similar to C++, isnt it? Yes there is something new: sc_main, SC_MODULE, and
SC_CTOR.
Every C/C++ program starts execution from main() function. However if SystemC library is
used then the program starts execution from sc_main(). main() function is called internally
in SystemC library. The arguments of sc_main() are similar to main() in C/C++.
SystemC programs consist of a set of one or more modules. They provide the ability to
describe structure and are similar to classes in C++. SC_MODULE(hello_world) define a
class which has a constructor SC_CTOR(hello_world) and a function say_hello().
3. Using SystemC
Modules
Modules are the basic building blocks in SystemC to partition a design. A module in basic
contains ports, internal data, constructors and functions to work on the ports. They are
defined as
SC_MODULE(module_name)
{
//module body
}
Ports
Ports pass data to and from the processes of a module to the external world. They are
declared with in, out, or inout. They are declared as
sc_in<bool> reset;
sc_inout<int> data;
Signals
Ports are used for the external communication and signals are used for the communication
inside the module. They can be declared as
sc_signal<bool> reset;
Process
Processes are functions that can be made to run continuously by making them as threads or
run whenever an event occurs.
Constructor
Same as in C++ and can be defined as
SC_MODULE(module_name)
{
SC_CTOR(module_name)
{
//body
}
}
4. CPU-Memory Example
We now have an idea of how a simple SystemC program looks like and how to execute it.
Lets write another program and this will help you to start your assignments during this lab
course.
In this example we simulate CPU connected directly to memory through a number of ports.
When the simulation starts, CPU reads addresses from the given trace file and forward them
to memory to read or write data. The memory receives the request from CPU and performs
the operation. CPU waits for the operation to be completed, and then issues the next
instruction. Let starts from looking at the code and try to understand it.
#include <systemc.h>
#include <iostream>
#include "aca2011.h"
#define DEBUG
using namespace std;
SC_MODULE(Memory)
{
public:
enum Function
{
FUNC_READ,
FUNC_WRITE
};
enum RetCode
{
RET_READ_DONE,
RET_WRITE_DONE,
};
sc_in<bool> Port_CLK;
sc_in<Function> Port_Func;
sc_out<RetCode> Port_Done;
sc_in<int> Port_Addr;
sc_inout_rv<32> Port_Data;
SC_CTOR(Memory)
{
SC_THREAD(execute);
sensitive << Port_CLK.pos();
dont_initialize();
}
~Memory()
{
}
private:
void execute()
{
while (true)
{
wait(Port_Func.value_changed_event());
Function f = Port_Func.read();
int addr = Port_Addr.read();
// Simulate Memory read/write delay
wait(100);
if (f == FUNC_READ)
{
// We return a fack data to CPU which is read from the requested
// address of memory
int data = 0;
Port_Data.write( data );
Port_Done.write( RET_READ_DONE );
stats_readhit(0);
#ifdef DEBUG
cout<<"@"<<sc_time_stamp()<<" Memory reads at address("<<addr<<"). "<<endl;
#endif
Port_Data.write("ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ");
}
else
{
stats_writehit(0);
#ifdef DEBUG
cout<<"@"<<sc_time_stamp()<<" Memory writes at address("<<addr<<"). "<<endl;
#endif
Port_Done.write( RET_WRITE_DONE );
}
}
}
};
SC_MODULE(CPU)
{
public:
sc_in<bool> Port_CLK;
sc_out<Memory::Function> Port_MemFunc;
sc_in<Memory::RetCode> Port_MemDone;
sc_out<int> Port_MemAddr;
sc_inout_rv<32> Port_MemData;
SC_CTOR(CPU)
{
SC_THREAD(execute);
sensitive << Port_CLK.pos();
dont_initialize();
}
private:
void execute()
{
TraceFile::Entry tr_data;
Memory::Function f;
// Loop until end of tracefile
while(!tracefile_ptr->eof())
{
// Get the next action for the processor in the trace
if(!tracefile_ptr->next(0, tr_data))
{
cerr << "Error reading trace for CPU" << endl;
break;
}
int addr = tr_data.addr;
int data = 0;
switch(tr_data.type)
{
case TraceFile::ENTRY_TYPE_READ:
f = Memory::FUNC_READ;
Port_MemAddr.write(addr);
Port_MemFunc.write(f);
#ifdef DEBUG
cout<<"@"<<sc_time_stamp()<<" CPU reads data from address("<<addr<<").
"<<endl;
#endif
break;
case TraceFile::ENTRY_TYPE_WRITE:
f = Memory::FUNC_WRITE;
//Write a random data to Memory
data = rand();
Port_MemData.write(data);
Port_MemAddr.write(addr);
Port_MemFunc.write(f);
#ifdef DEBUG
cout<<"@"<<sc_time_stamp()<<" CPU writes data at address("<<addr<<").
"<<endl;
#endif
Port_MemData.write("ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ");
break;
case TraceFile::ENTRY_TYPE_BAR:
#ifdef DEBUG
cout<<"Memory Barrier .. synchronize all processors"<<endl;
#endif
continue;
//break;
case TraceFile::ENTRY_TYPE_END:
cout<<"The trace for this file is
terminated"<<endl;
break;
default:
cerr << "CPU 0 "<<" ERROR" << endl;
exit(0);
}
wait(Port_MemDone.value_changed_event());
// Advance one cycle in simulated time
wait();
}
// Finished the tracefile, now stop the simulation
sc_stop();
}
};
int sc_main(int argc, char* argv[])
{
try
{
// Initialize the trace file
init_tracefile(&argc, &argv);
if(num_cpus > 1)
{
cout<<"This test program can handle only single cpu's trace
file"<<endl;
exit(0);
}
// Initialize statistics counters
stats_init();
// Instantiate Modules
Memory mem("main_memory");
CPU cpu("cpu");
// The clock that will drive the CPU and Memory
sc_clock clk;
mem.Port_CLK(clk);
cpu.Port_CLK(clk);
// Signals
sc_buffer<Memory::Function> sigMemFunc;
sc_buffer<Memory::RetCode> sigMemDone;
sc_signal<int> sigMemAddr;
sc_signal_rv<32> sigMemData;
// Connecting module ports with signals
mem.Port_Func(sigMemFunc);
mem.Port_Done(sigMemDone);
mem.Port_Addr(sigMemAddr);
mem.Port_Data(sigMemData);
cpu.Port_MemFunc(sigMemFunc);
cpu.Port_MemDone(sigMemDone);
cpu.Port_MemAddr(sigMemAddr);
cpu.Port_MemData(sigMemData);
// Start Simulation
sc_start();
// Print statistics after simulation finished
stats_print();
stats_cleanup();
}
catch (exception& e)
{
cerr << e.what() << endl;
}
return 0;
}
Memory Module
The Memory Module simulates a Random Access Memory. CPU issues instruction to read
or write data at given addresses and this module performs the requested operation.
The first elements of the modules declaration are two enumeration types. The first is used
to identify the read or write request by CPU and the second is used to send signal back to
CPU when it is done with reading or writing.
The next five lines are the declarations of ports the module has in order to communicate
with other modules. Ports are objects through which a module can be connected into one or
more channels. The ports used in the example are: sc_in, sc_out and sc_inout_rv. These
ports can handle data of any C++ or SystemC data-type.
SC_CTOR is the constructor of the module. As a module has processes, which are member
functions or threads registered as processes to the simulation kernel and executed by it when
an event is triggered. The first line of the constructors code does exactly that. It registers to
the simulation kernel that the Memory module has a process which is a thread, the code of
which is the execute member function.
Processes have a list of events on which they are sensitive. If an event happens on a
process sensitivity list, they get woken up. The second line of the constructors code
sensitive << Port_CLK.pos() creates that list for the process registered by the previous
SC_THREAD statement. It specifies that this process will execute on the event when the
signal on the Port_CLK port goes positive (i.e. once per clock cycle).
Events are the basic synchronization objects. They are used to synchronize between
processes. Processes are triggered or caused to run based on their sensitivity to events. That
means that every time a module member function is registered as a process, the events on
which it is sensitive are also defined.
Process gets executed automatically in constructor even if event in sensitivity list does not
occur yet. To prevent this unintentional execution, use dont_initialize() function as shown
in example.
The execute member function defines the code for the thread that will run for this module,
as defined in the constructor. The thread continuously waits for the Port_Func to get written
and then serves the request. It reads the address and data if its a memory read, then waits
100 cycles to simulate memory latency and finally writes the result back before waiting on
the Port_Func again.
Note that a string of 32 Zs is written to the 32-bit bi-directional data port a cycle after we
write the data to the port. This has to be done because bi-directional channels, which have
multiple writers, attempt to resolve conflicts when multiple writers are writing values. Since
we have now written a value, we need to reset our end of the channel in the next cycle to
allow the other end to write values for the next request.
CPU Module
This module simulates a CPU that has the appropriate ports to connect to our Memory
module and make read/write requests for random addresses. In this module we uses sc_in
port for every sc_out port of the Memory module, and a sc_out for every sc_in. The
constructor and execute member function of the module is similar to the one of the Memory
module.
tracefile_ptr is a global pointer to the trace file to read addresses from. Functions
init_tracefile() and stat_init() and stat_print() are explained in Appendix B.
sc_main()
In the sc_main() function the structural elements of the system are created and connected
throughout the system hierarchy. This is facilitated by the C++ class object construction
behavior. When a module comes into existence all sub-modules it contains are also created.
We create four channels of type sc_signal and sc_buffer and connect the two modules
through their ports to these signals. We also create an instance of the sc_clock class and
connects that to the Port_CLK ports of the CPU and Memory modules. The sc_clock class
is a predefined primitive channel derived from the class sc_signal and is intended to model
the behavior of a digital clock signal. In effect an instance of sc_clock triggers an event in
regular intervals (there are constructor overloads that can be used to set certain properties to
the clock. When a modules port is connected to the clock the process sensitive to the port is
executed.
After all modules have been created and connections have been established the simulation
can start by calling the function sc_start() where the simulation kernel takes control and
executes the processes of each module depending on the events occurring at every
simulated cycle. In the first cycle all the processes are executed at least once unless
otherwise stated with a call to the function dont_initialize() when the process is registered.
Time starts at 0 and moves forward only. Time increments are based on the default time
unit and the time resolution. Once the simulation starts there must be a terminating point
where you call sc_stop() function. In our example code we call sc_stop() when we reach
the end of the tracefile.
7. Conclusion
Until now we have seen how a basic simulator can be built using SystemC and the basic
aspects of the framework. A complete implementation of this example can be downloaded
from:
http://www.science.uva.nl/~jianfu/aca2011/aca2011.html
More detailed information on the topics described here and many others can be found in the
document: 1666-2005 IEEE Standard SystemC