Identifying SystemC constructs
SystemC Module Declarations
There are two ways in which a SystemC module can be declared: (1) using the SC_MODULE()
macro or (2) inheriting from the sc_module
class.
Both of these approaches are recognized by systemc-clang.
1#include <systemc.h>
2SC_MODULE(counter) {
3// Some other code here.
4
5};
1#include <systemc.h>
2class counter: public sc_module {
3// Some other code here.
4
5};
SystemC Constructor
There are two ways in which the constructor can be used: (1) using the SC_CTOR()
macro or (2) using the class constructor.
Both of approaches are recognized by systemc-clang.
1#include <systemc.h>
2SC_MODULE(counter) {
3 sc_uint<32> keep_count;
4 SC_CTOR(counter): keep_count{0} {
5 // Some other code here.
6 }
7};
1#include <systemc.h>
2class counter: public sc_module {
3 sc_uint<32> keep_count;
4 counter(): keep_count{0} {
5 // Some other code here.
6 }
7};
SystemC Processes and Sensitivity Lists
There are two process types that systemc-clang recognizes: (1) SC_METHOD()
and (2) SC_THREAD()
.
The only synthesizable process type is SC_METHOD()
.
systemc-clang gives access to the function provided as the argument to the process macro.
Plugins can then use tihs to perform other operations on the implementation of the process’ function such as generating Verilog or an intermediate representation.
1#include <systemc.h>
2SC_MODULE(counter) {
3 sc_in_clk clk;
4 sc_uint<32> keep_count;
5
6 SC_CTOR(counter): keep_count{0} {
7 SC_METHOD(count_up);
8 sensitive << clk.pos();
9 }
10
11 void count_up() {
12 // Some code here.
13 }
14
15};
1#include <systemc.h>
2class counter: public sc_module {
3 sc_in_clk clk;
4 sc_uint<32> keep_count;
5
6 SC_HAS_PROCESS(counter);
7 counter(): keep_count{0} {
8 SC_METHOD(count_up);
9 sensitive << clk.pos();
10 }
11
12 void count_up() {
13 // Some code here.
14 }
15};
SystemC Ports and Member Variables
SystemC ports and member fields can be a part of the module. In the counter example, there is a clk
port of type sc_in_clk
and there is a member variable called keep_count
of type sc_uint<32>
. Note that the latter is a templated type. We extend this example further.
Note that a member variable can also be an sc_signal<>
, which we show in the next subsection.
1#include <systemc.h>
2SC_MODULE(counter) {
3 // clock
4 sc_in_clk clk;
5
6 // output port
7 sc_out<sc_uint<32>> count_out;
8
9 // member variable
10 sc_uint<32> keep_count;
11
12 SC_CTOR(counter) {
13 SC_METHOD(count_up);
14 sensitive << clk.pos();
15 }
16
17 void count_up() {
18 keep_count = keep_count + 1;
19 count_out.write( keep_count );
20 }
21
22};
Nested Module Declarations & Port Binding
It is common to use a hierarchy of modules to describe a design. systemc-clang identifies this hierarchy.
For the example below, the nested module of type counter
will be identified as being nested within the SystemC module of type DUT
.
Within the constructor of the DUT
module, we are instantiating the count
member variable with a given name, and then binding the ports to the appropriate signals.
This example also shows the use of a sc_signal<>
.
1#include <systemc.h>
2// Code from before
3SC_MODULE(counter) {
4};
5
6// Top level module.
7SC_MODULE(DUT) {
8 counter count;
9 sc_clk clock;
10 sc_signal< sc_uint<32> > counter_out;
11
12 SC_CTOR(DUT): count{"counter_instance"} {
13 // port bindings
14 count.clk(clock);
15 count.count_out(counter_out);
16 };
17};
18
19int sc_main() {
20 DUT dut{"design_under_test"};
21};
Templated Modules
The SystemC module declaration can be templated as well. We can extend our counter
module to have it access a template argument.
1#include <systemc.h>
2template <typename T>
3SC_MODULE(counter) {
4 // clock
5 sc_in_clk clk;
6
7 // output port
8 sc_out<T> count_out;
9
10 // member variable
11 T keep_count;
12
13 SC_CTOR(counter) {
14 SC_METHOD(count_up);
15 sensitive << clk.pos();
16 }
17
18 void count_up() {
19 // Ensure that type T supports +
20 keep_count = keep_count + 1;
21 count_out.write( keep_count );
22 }
23
24};
User-defined Templated Datatypes
Note
Todo
User-defined Channels
Note
Todo
Typedefs and using
Typedefs and the use of type alias via using
are parsed by systemc-clang.
The parsing drills down to the most basic type, and desugars any sugared type.
This means that any intermediate type (via indirection) is not captured, but only the final desugared type.
systemc-clang uses Catch2 to implement tests. There are several examples under
tests/
that can be viewed. This part of the documentation will go through creating a simple test, and integrating it into the build system.In order to run systemc-clang from within a C++ program, and integrate it successfully into the build environment, we describe a few boilerplate items that are essential.
The boilerplate allows one to integrate tests into the build environment for systemc-clang.
We describe the
#include
files that are necessary. We must include the main header file from where we can invoke theSystemCConsumer
calledSystemCClang.h
. We also include thecatch.hpp
, which is necessary to use the features of Catch2. Finally, there is an include file that is automatically generated calledClangArgs.h
. This is necessary to pass the arguments provided to the build environment to the test.1#include "SystemCClang.h" 2#include "catch.hpp" 3 4// This is automatically generated from cmake. 5#include "ClangArgs.h"systemc-clang is wrapped in a namespace called
scpar
. Hence, we add the namespace.1using namespace scpar;The next step is in providing the SystemC source that we wish to parse. This can come from a file or embedded within the test program as a string. Note that this uses the
R"
approach to defining astd::string
.1 std::string code = R"( 2 #include <systemc.h> 3 SC_MODULE(counter) { 4 // clock 5 sc_in_clk clk; 6 7 // output port 8 sc_out<sc_uint<32>> count_out; 9 10 // member variable 11 sc_uint<32> keep_count; 12 13 SC_CTOR(counter) { 14 SC_METHOD(count_up); 15 sensitive << clk.pos(); 16 } 17 18 void count_up() { 19 keep_count = keep_count + 1; 20 count_out.write( keep_count ); 21 } 22 23 }; 24 25 // Top level module. 26 SC_MODULE(DUT) { 27 counter count; 28 sc_clock clock; 29 sc_signal< sc_uint<32> > counter_out; 30 31 SC_CTOR(DUT): count{"counter_instance"} { 32 // port bindings 33 count.clk(clock); 34 count.count_out(counter_out); 35 }; 36 }; 37 38 int sc_main() { 39 DUT dut{"design_under_test"}; 40 return 0; 41 }; 42 )";We can start creating our test using Catch2’s
TEST_CASE()
. Please refer to the Catch2 documentation the usage of the specific Catch2 macros. They key systemc-clang aspect we point out is that the tests create the AST from within the C++ program usingASTUnit
as shown below. The arguments tobuildASTFromCodeWithArgs()
include the string created earlier, and the arguments to successfully create the AST. For example, the location of the SystemC header files. These are captured insystemc_clang::catch_test_args
as a part of theClangArgs.h
.1TEST_CASE("Basic parsing checks", "[parsing]") { 2 ASTUnit *from_ast = 3 tooling::buildASTFromCodeWithArgs(code, systemc_clang::catch_test_args) 4 .release(); 5 // Some more code here 6}If the SystemC model has compile errors, then they will be shown on the standard output. Otherwise,
from_ast
will point to the AST of the SystemC model.Now that we have access to the AST, we can run systemc-clang. Inline comments describe each of the lines.
1// Instantiate consumer. 2SystemCConsumer consumer{from_ast}; 3 4// Run systemc-clang. 5consumer.HandleTranslationUnit(from_ast->getASTContext()); 6 7// Get access to the internal model. 8Model *model{consumer.getSystemCModel()}; 9 10// Get instance with name "counter_instance". 11ModuleDecl *test_module{model->getInstance("counter_instance")};The key observation is that
model
provides access to the parsed information. This includes all the modules that were instantiated, the ports and signals within it, any nested sub-modules, etc.The test can use
REQIRE()
macro from Catch2 to assert for the information found about the model. The following assertion ensures that we are able to find an instance of a SystemC module that has the namecounter_instance
.1 REQUIRE(test_module != nullptr);We can also check for the number of input ports found by systemc-clang, and likewise, the number of output ports.
1 auto input_ports{test_module->getIPorts()}; 2 REQUIRE(input_ports.size() == 1);Similarly, using other member functions of the systemc-clang’s
Model
class, one can access other components of a module instance.1 auto output_ports{test_module->getOPorts()}; 2 REQUIRE(output_ports.size() == 1);
Writing matchers with clang-query
clang-query
is a clang tool that allows one to write AST matchers and test them.
Using clang-query
to write matchers to identify SystemC constructs is quite beneficial.
Warning
There are some differences in what clang-query
accepts, and what the clang tool actually accepts.
As a result, not all matchers that work in clang-query
will compile in the clang tool.
Nonetheless, clang-query
is an excellent way to start experimenting with writing a matcher.
Pre-requisites
Ensure that the following environment variables have been set.
* SYSTEMC
An example: Matching SystemC module declarations
The best way to illustrate the use of clang-query
is with examples.
Suppose we have the following SystemC module declaration.
#include <systemc.h>
SC_MODULE(counter) {
// clock
sc_in_clk clk;
// output port
sc_out<sc_uint<32>> count_out;
// member variable
sc_uint<32> keep_count;
SC_CTOR(counter) {
SC_METHOD(count_up);
sensitive << clk.pos();
}
void count_up() {
keep_count = keep_count + 1;
count_out.write( keep_count );
}
};
// Top level module.
SC_MODULE(DUT) {
counter count;
sc_clock clock;
sc_signal< sc_uint<32> > counter_out;
SC_CTOR(DUT): count{"counter_instance"} {
// port bindings
count.clk(clock);
count.count_out(counter_out);
};
};
int sc_main() {
DUT dut{"design_under_test"};
return 0;
};
Without a doubt, anyone who wishes to write an AST matcher must review the list of matchers that are available here: AST Matchers Reference.
To run this example, we can execute it in the following way (assuming we are in the root of the systemc-clang directory).
1$ clang-query -extra-arg=-I$SYSTEMC/include docs/source/matcher/counter.cpp
The -extra-arg
option in clang-query
specified the path for SystemC includes.
You will come to a clang-query
prompt.
This is where we can write our matcher.
Let us start by identifying the declarations of the SystemC modules in the model.
These would be the counter
and DUT
modules.
In order to do this, we need to understand that the SC_MODULE()
macro essentially declares a class that is derived from the sc_module
class.
As a starter, we could write the following matcher.
1match cxxRecordDecl()
Note that the command match
tells that the string following it is the matcher to execute.
When you run the above command, you will notice that this produces quite a few matches (around nine thousand of them).
This is because the matcher is identifying all C++ declarations in the AST, which includes the SystemC library.
If we want to limit ourselves to only matching the file provided, then we can use the isExpansionInMainFile()
. Please refer to the AST matcher reference to learn more about it.
1match cxxRecordDecl(isExpansionInMainFile())
This matcher will produce four matches of the two SystemC modules in the model.
Match #1:
/home/twiga/code/github/systemc-clang/docs/source/developer/matchers/counter.cpp:3:1: note: "root" binds here
SC_MODULE(counter) {
^~~~~~~~~~~~~~~~~~~~
/home/twiga/code/systemc-2.3.3/systemc/include/sysc/kernel/sc_module.h:397:5: note: expanded from macro 'SC_MODULE'
struct user_module_name : ::sc_core::sc_module
^
Match #2:
/home/twiga/code/github/systemc-clang/docs/source/developer/matchers/counter.cpp:3:1: note: "root" binds here
SC_MODULE(counter) {
^~~~~~~~~~~~~~~~~~
/home/twiga/code/systemc-2.3.3/systemc/include/sysc/kernel/sc_module.h:397:5: note: expanded from macro 'SC_MODULE'
struct user_module_name : ::sc_core::sc_module
^~~~~~~~~~~~~~~~~~~~~~~
Match #3:
/home/twiga/code/github/systemc-clang/docs/source/developer/matchers/counter.cpp:26:1: note: "root" binds here
SC_MODULE(DUT) {
^~~~~~~~~~~~~~~~
/home/twiga/code/systemc-2.3.3/systemc/include/sysc/kernel/sc_module.h:397:5: note: expanded from macro 'SC_MODULE'
struct user_module_name : ::sc_core::sc_module
^
Match #4:
/home/twiga/code/github/systemc-clang/docs/source/developer/matchers/counter.cpp:26:1: note: "root" binds here
SC_MODULE(DUT) {
^~~~~~~~~~~~~~
/home/twiga/code/systemc-2.3.3/systemc/include/sysc/kernel/sc_module.h:397:5: note: expanded from macro 'SC_MODULE'
struct user_module_name : ::sc_core::sc_module
^~~~~~~~~~~~~~~~~~~~~~~
4 matches.
You will quickly note that clang-query
doesn’t really provide a nice interface to go back to the previous command and edit it.
Consequently, it is better to use a file to provide as an input to it with the matcher we wish to write.
Suppose that we create a separate file called control.dbg
, which contains our matcher.
We can then execute the script in the following way.
1$ clang-query -extra-arg=-I$SYSTEMC/include docs/source/matcher/counter.cpp -f control.dbg
You will notice that we have multiple matches (more then 2), and we should only be having two matches for the two SystemC modules.
To correct this, we have to make two changes:
* First is to inform clang-query
that we should only receive matches on nodes that we have bound a string to, and disable outputting any other matches that are not specific to the strings the matcher binds. We would do this by using set bind-root false.
* Second, we have to remove implicit nodes that are created in the clang AST.
If we want to write a matcher that refers to the identified AST nodes, we have to bind the nodes to a string that we can then use to extract them. We can update our matcher to add a binding string.
1match cxxRecordDecl(unless(isImplicit()), isExpansionInMainFile()).bind("modules")
Note
You may have noticed that we are finding the SystemC module declarations twice. The reason for this is that clang creates an implicit referenced node for each of the classes. To remove this from our matches, we need to include the unless(isImplicit()) predicate to the matcher.
This will produce two matches, which correctly identify the counter
and DUT
SystemC modules.
If you wish to display the AST nodes for these matches, then you can add a command to control the output. This would be done with set output dump
:
1set bind-root false
2set output dump
3match cxxRecordDecl(unless(isImplicit()), isExpansionInMainFile()).bind("modules")
The output has not been shown, but hopefully you were able to see the AST dump.
Something that you may quickly notice that the matcher written would also identify classes that are not SystemC modules.
In order to enforce that, we need to state that the class must be derived from the sc_module
class.
We would update our matcher in the following way.
1set bind-root false
2set output dump
3match cxxRecordDecl(isDerivedFrom("::sc_core::sc_module"), unless(isImplicit()), isExpansionInMainFile()).bind("modules")
If you were to go and add non-SystemC classes to the original model, you will see that those will not be matched.