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 the SystemCConsumer called SystemCClang.h. We also include the catch.hpp, which is necessary to use the features of Catch2. Finally, there is an include file that is automatically generated called ClangArgs.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 a std::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 using ASTUnit as shown below. The arguments to buildASTFromCodeWithArgs() 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 in systemc_clang::catch_test_args as a part of the ClangArgs.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 name counter_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.