Learning SystemC: #002 Module – sc_module
In this post I will talk about the SystemC module.
Here is a list of content if you want to jump to a particular subject:
1. What Is a SystemC Module?
2. SystemC Module Declaration
2.1. Declaration Macro – SC_MODULE
2.2. Constructor Macro – SC_CTOR
2.3. Thread Macro – SC_THREAD
2.4. Elaboration And Simulation Callbacks
3. Putting It All Together
3.1. Template for Coding in SystemC
3.2. How to Start a Simulation
1. What Is a SystemC Module?
SystemC introduces the notion of module.
A SystemC module is nothing more than a class – sc_module. It has inside a lot of logic which make it possible for us to model in C++ some hardware components. We will discover step by step, in this lesson and the next to come, what functionality is inside a sc_module class.
You can think of sc_module as the base class for all the building blocks of a hardware system that you want to model in SystemC.
For example, if you want to model a CPU, then you will have to have a class, let’s call it cpu which extends from sc_module.
A SystemC module can split its functionality into multiple sub-modules. So our cpu class can have inside some class members called alu and decoder to model an “Arithmetic Logic Unit” and a “Decoder Unit”.
All these cpu class members are themselves classes extending from sc_module. They too might have inside other sub-modules or simply contain the logic necessary to model the desired functionality.
2. SystemC Module Declaration
In this section we will see how we can declare a simple module, called cpu, how we can declare its constructor and how we can implement a simple functionality in it.
2.1. Declaration Macro – SC_MODULE
It is very easy to declare a SystemC module. You can take advantage of the SC_MODULE macro:
1 2 3 4 5 | #include <systemc.h> SC_MODULE(cpu) { //add here the code related to cpu SystemC module }; |
The SC_MODULE macro has a very simple definition:
1 2 | #define SC_MODULE(user_module_name) \ struct user_module_name : ::sc_core::sc_module |
It is not mandatory to use the SC_MODULE macro but it is recommended.
So it is the same thing if you declare the directly the cpu class in this manner:
1 2 3 4 5 | #include <systemc.h> class cpu : sc_module { //add here the code related to cpu SystemC module }; |
2.2. Constructor Macro – SC_CTOR
There are a lot of things that a SystemC module can have: ports, channels, constructors, destructors, sub-modules etc. Most of them are optional except constructors: a module must have a constructor declared.
A constructor of a SystemC module must do several tasks:
- initializing sub-modules
- connecting sub-modules
- registering processes with the SystemC kernel
- providing static sensitivity
- user-defined tasks
We will go through all these in the lessons to come. For now we will focus only on how to declare a constructor.
The SystemC library comes with a helper macro SC_CTOR so we can easily declare a constructor:
1 2 3 4 5 6 7 | #include <systemc.h> SC_MODULE(cpu) { SC_CTOR(cpu) { cout << "cpu::constructor()" << endl; }; }; |
The declaration of the SC_CTOR macro is this:
1 2 3 | #define SC_CTOR(user_module_name) \ typedef user_module_name SC_CURRENT_USER_MODULE; \ user_module_name( ::sc_core::sc_module_name ) |
It is not mandatory to use the SC_CTOR macro but it is recommended.
One drawback of the SC_CTOR macro is that you can not add user defined arguments to the created contructor.
For this you have to declare the constructor directly but make sure that you initialize the module base class with the name of the module:
1 2 3 4 5 6 7 8 9 10 11 12 13 | #ifndef CPU_H #define CPU_H #include <systemc.h> SC_MODULE(cpu) { //you should call this macro if you do not use SC_CTOR SC_HAS_PROCESS(cpu); //declare a constructor with custom arguments cpu(sc_module_name name, int cache_size); }; #endif |
1 2 3 4 5 6 7 8 9 10 | #include <systemc.h> #include "cpu.h" //make sure you do not forget to call the base constructor with the name of the instance cpu::cpu(sc_module_name name, int cache_size) : sc_module(name) { cout << "cpu::constructor()" << endl; //use the custom argument to build the cache build_cache(cache_size); }; |
2.3. Thread Macro – SC_THREAD
A SystemC thread is basically a C++ function which is automatically called by the SystemC kernel.
We will discuss threads in a later lesson, but for now we will use a SystemC thread just to attach some action to a module.
There are two things that we need to do:
- define the function
- register the function to the SystemC kernel so that it will know our function is a thread
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #include <systemc.h> SC_MODULE(cpu) { public: //declare the function which will be registered to the kernel as a SystemC thread void cpu::my_custom_function() { cout << "cpu::my_custom_function()" << endl; }; SC_CTOR(cpu) { cout << "cpu::constructor()" << endl; //register the thread //this thread will be called once sc_start() is called SC_THREAD(my_custom_function); }; }; |
2.4. Elaboration and Simulation Callbacks
The SystemC module class sc_module comes with four empty callback functions which are called during varios stages of elaboration and simulation. There functions are:
In your modules you can override these callback functions to do various actions and rely on the kernel to call those functions for you.
If you want to find out more details about these callback functions I suggest to go to the Language Reference Manual and read about their usage recommandations. But at this stage in our learning process I think it is enough just to know about their existence.
3. Putting It All Together
In this section I will present a template for coding SystemC modules and then we will see how we can start a simulation.
3.1. Template for Coding in SystemC
Because SystemC is just a C++ library, SystemC programmers should follow the same coding guidelines used in C++ world. This imply the existence of two files: a header file (.h) and a compiled file (.cpp).
Let’s see what these two files should contain, in an abstract manner, and how it apply to our cpu module.
The header file should contain the class members declarations, constructor(s) and declaration of all the functions:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | #ifndef NAME_H //use this to protect against multiple includes #define NAME_H //include here all the sub-modules or your module #include "submodule.h" SC_MODULE(NAME) { Port declarations Channel and submodule instances SC_CTOR(NAME) : Initializations { Connectivity Process registrations } //declare here all the processes and helper functions Function declarations }; #endif |
The compiled file should contain the implementation of all the functions declared in the header:
1 2 3 4 5 6 7 | #include <systemc.h> #include "NAME.h" //implement here all the functions declared in the header NAME::Function { implementation } |
Let’s see how the cpu module looks like using this coding style.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | #ifndef CPU_H #define CPU_H #include "alu.h" #include "decoder.h" SC_MODULE(cpu) { public: //Decoder sub-module decoder decoder_inst; //ALU sub-module alu alu_inst; SC_CTOR(cpu) : decoder_inst("decoder_inst"), alu_inst("alu_inst") { cout << "cpu::constructor()" << endl; //register the thread //this thread will be called once sc_start() is called in testbench.cpp SC_THREAD(my_custom_function); }; protected: //declare the thread function virtual void my_custom_function(); //declare all the callbacks in order to be able to override their implementation in cpu.cpp file virtual void before_end_of_elaboration(); virtual void end_of_elaboration(); virtual void start_of_simulation(); virtual void end_of_simulation(); }; #endif |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | #include <systemc.h> #include "cpu.h" void cpu::my_custom_function() { cout << "cpu::my_custom_function()" << endl; }; void cpu::before_end_of_elaboration() { cout << "cpu::before_end_of_elaboration()" << endl; } void cpu::end_of_elaboration() { cout << "cpu::end_of_elaboration()" << endl; } void cpu::start_of_simulation() { cout << "cpu::start_of_simulation()" << endl; } void cpu::end_of_simulation() { cout << "cpu::end_of_simulation()" << endl; } |
I’ve added some implementation for all the callback functions so I can see in what order they are called. I did a similar implementation for both decoder and alu sub-modules.
You can run the full example on EDA Playground.
3.2. How to Start a Simulation
The starting point in a SystemC program is a function called sc_main().
In the end SystemC is just a C++ library and the well known main() function does exists and it is calling sc_main() function defined by the user but this is declared somewhere in the SystemC library itself.
To follow the conventions, you should declare your sc_main() function in a file called main.cpp. The content of the main.cpp file should look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 | int sc_main(int argc, char* argv[]) { //ELABORATION phase - everything before sc_start() function //create here instance of modules, clocks, perform connections etc //SIMULATION phase - just call sc_start() function sc_start(); //POST-PROCESSING phase //handle here the results of the simulation, close stimuli files etc //return '0' only for a successful simulation return EXIT_CODE; } |
Based on the declaration of the sc_main() function we can identify three phases:
- Elaboration Phase – everything before the call of sc_start() function. This phase should be used to declare modules, clocks, make connections etc.
- Simulation Phase – the execution of the sc_start() function
- Post-processing Phase – the code after sc_start(). In this phase you should handle the results of the simulation (e.g. determine if a test passed or failed), close the stimuli files etc.
For our example of the cpu module the main.cpp file looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | #include "systemc.h" #include "cpu.h" using namespace sc_dt; int sc_main(int, char* []) { cout << "sc_main() ..." << endl; //make sure you call the appropiate constructor (e.g. with the name of the module) cpu cpu_inst("cpu_inst"); sc_start(); //call this function in order to trigger end_of_simulation() calls if(not sc_end_of_simulation_invoked()) { cout << "Simulation stopped without explicit sc_stop()" << endl; sc_stop(); } return 0; } |
You can run the full example on EDA Playground.
That’s it for the introduction of the SystemC module. My recommendation is to run the example on EDA Playground and take notice of the order of the prints from the constructors and the callbacks.
Hope you found this lesson useful 🙂
Next Lesson: Learning SystemC: #003 Time, Events and Processes
Previous Lesson: Learning SystemC: #001 Data Types