Jump to content

Problems with SystemC syntax, improvment request


Roman Popov

Recommended Posts

Hello, 

Is any facelift planned for next SystemC release (2.4 or 3.0)?

 

I want to share some feedback , from synthesizable SystemC designer prospective. 

 

Recently I’ve done a lot of synthesizable SystemC coding for HLS (High level synthesis tools). I really enjoy the power of synthesizable stateful threads (SC_THREADS), it helps a lot in converting algorithmic C++ code into RTL.  TLM abstraction of synthesizable bus interfaces works well too.

However, If you look at low level syntax details, SystemC code looks really ugly comparing to Verilog.  Consider a simple example:

module adder (
  input [7:0] a, b,
  output [8:0] res
  );

  assign res = a + b;

endmodule

module adder_test;
  reg [7:0] a, b;
  wire [8:0] res;

  adder adder_isnt(
    .a(a),
    .b(,
    .res(res)
  );

  initial begin
    a = 11; b = 31;
    #1;
    $display("%d",res);
  end

endmodule

If I rewrite in SystemC it would be:

 
#include <systemc.h>

SC_MODULE(adder) {

   sc_in<uint8_t>      a, b;
   sc_out<sc_uint<9> > sum;

   SC_CTOR(adder)
   : a{"a"}
   , b{"b"}
   , sum{"sum"}
   {
       SC_METHOD(add_method);
       sensitive << a << b;
   }

   void add_method() {
       sum = a + b;
   }
};

SC_MODULE(adder_test) {

   sc_signal<uint8_t>     a, b;
   sc_signal<sc_uint<9>>  sum;

   adder add_inst;

   SC_CTOR(adder_test)
   : a("a")
   , b("b")
   , sum("sum")
   , add_inst("add_inst")
   {
       add_inst.a(a);
       add_inst.b(;
       add_inst.sum(sum);

       SC_THREAD(test_thread);
   }

   void test_thread() {
       a = 11; b = 31;
       wait(1, SC_PS);
       cout << sum.read() << endl;
       sc_stop();
   }

};

Main problem is not that SystemC code has more charaters/LOCs, but that semantically related statements are distributed across source file.

 

Take for example adder instantiation:

  • First we have to declare member variable:

  adder add_inst;
  • Next we need to initialize its name:

  : add_inst("add_inst")
  • And finally bind its ports:

add_inst.a(a);
add_inst.b(;
add_inst.sum(sum);

Because of this syntactic problem it is very hard to read SystemC code when you have a lot of signals and modules instantiated.  

 

Same problem with SC_METHOD and SC_THREAD:

You have to define method in one part of code, and declare it as a SC_METHOD/SC_THREAD in another.  I often forgot to put this SC_THREAD macro and my simulations do not work.

Forgetting sc_module_name initialization is even more common (but does not do that much damage).

 

So there are two problems with SystemC syntax:

  • Code hard to read.
  • Easy to forget something.

 

I hope this will be fixed soon.  If I start to think about it , I see two possible options:

 

 

Option 1: Preprocessing

 

First solution would be creating some SystemC preprocessor. It's not that uncommon idea in C++ world.  For example widely used Qt Framework (http://www.qt.io/) uses it’s own preprocessor. Some EDA SystemC tools use it too: Forte Cynthesizer used preprocessor to extract uArch constraints from some vendor predefined Macros,  I think Cadence Stratus inherited this HLS Macro idea too (have not tried it yet).


The obvious argument against this would be breaking C++ tools compatibility (for example C++ IDE front-ends). But it’s not always the case: Qt and HLS preprocessors do not break C++ syntax,  syntactically valid SystemC code will stay a valid C++ code.

But some C++ static analysis tools may break: for example, if we generate body of sc_module constructors in preprocessor, than we will have some “unreachable” code, “unused” methods detected in human-written code.


Second argument against preprocessor is that user will need to integrate additional step into C++ build process. Sometimes it hurts, many users do not like to write custom build steps.


Main benefit in preprocessing is that it solves all the problems. It would be even possible to automatically generate sensitivity lists for SC_METHODS (in that case it it would be very smart Clang-based preprocessor), highly desired.

http://forums.accellera.org/topic/5430-what-is-always-verilig2001-equivalent-syntax-in-systemc/

 

Option 2:  Improve SystemC library

 

Many things can be done simply by improving SystemC library.  In that case we will be limited by C++ meta-programming capabilities.


Ultimate solution to sc_object naming will be C++ reflection:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4451.pdf

But my bet it would not be standardized in next five years or so. Probably some EDA or semiconductor company can devote some resources to make it happen sooner?


Many things are better out-of-the box in C++11,

For example in-class initializers:

sc_signal<uint8_t>     a{"a"}, b{"b"};
sc_signal<sc_uint<9>>  sum{"sum"};

I use it now in all of my SystemC verification code.  Unfortunately not yet supported by HLS tool I use.


David Black proposed solution for assign

http://nascug.org/events/17th/black_cpp11_2_27_2012.pdf

Same idea with lambda can be applied to every SC_METHODS/SC_THREAD processes.  



Binding instance ports can be implemented in the same place where instance defined.

Consider for example passing “bind” lambda using in-class initializer:

 
#define SC_CTOR(user_module_name)                                           \
  typedef std::function<void(user_module_name& self)> bind_func_t;          \
  typedef user_module_name SC_CURRENT_USER_MODULE;                          \
  user_module_name( ::sc_core::sc_module_name, bind_func_t bindf= 0)        \
  { if (bindf) { bindf(*this); }

#define BIND_INST(module_type) \
  [&](module_type& i)

Summing all together improved SystemC can probably look like:

 

SC_MODULE(adder) {
      sc_in<uint8_t>       a{"a"}, b{"b"};
      sc_out<sc_uint<9> >  sum{"sum"};

      SC_CTOR(adder) {
           ASSIGN( a|b, sum = a + b; );
      }
};

SC_MODULE(adder_test) {
      sc_signal<uint8_t>     a{"a"}, b{"b"};
      sc_signal<sc_uint<9>>  sum{"sum"};

      adder add_inst{"add_inst", BIND_INST(adder) {
              i.a(a);
              i.b(;
              i.sum(sum);
          }};

      SC_CTOR(adder_test) {          
          SC_THREAD_LAMBDA(test_thread) {
              a = 11; b = 31;
              wait(1, SC_PS);
              cout << sum.read() << endl;
              sc_stop();
          }
      }
};
 
Link to comment
Share on other sites

There is a problem with implementation of in-place binding I've suggested in previous post: it does not respects the fact that signals may not be constructed by the time biding function is executed.

 

So it is better to delay binding until everything is constructed. Here is better implementation:

 struct my_sc_module : public sc_module {    
    typedef std::function<void()> bind_func_t;
    bind_func_t bf;


    my_sc_module( bind_func_t bind_func ) : bf(bind_func)  {}


    void before_end_of_elaboration() override {
        if (bf) bf();
    }
};


#define SC_MODULE(user_module_name)                                           \
    struct user_module_name : my_sc_module


#define SC_CTOR(user_module_name)                                             \
    typedef user_module_name SC_CURRENT_USER_MODULE;                          \
    user_module_name( ::sc_core::sc_module_name , bind_func_t bind_func = 0) : my_sc_module(bind_func)




SC_MODULE(adder) {


    SC_CTOR(adder) {
        SC_METHOD(add_method);
        sensitive << a << b;
    }


    void add_method() { sum = a + b; }


    sc_in<uint8_t> a{"a"}, b{"b"};
    sc_out<sc_uint<9> > sum{"sum"};


};


SC_MODULE(adder_test) {


    sc_signal<uint8_t> *a;




    adder add_inst{"add_inst", [&]() {
        add_inst.a(*a);
        add_inst.b(;
        add_inst.sum(sum);
    } };




    SC_CTOR(adder_test) {
        a = new sc_signal<uint8_t>("a");
        SC_THREAD(test_thread);
    }


    void test_thread() {
        a->write(11);
        b = 31;
        wait(1, SC_PS);
        cout << sum.read() << endl;
        sc_stop();
    }


    sc_signal<uint8_t> b{"b"};
    sc_signal<sc_uint<9>> sum{"sum"};


};




int sc_main(int, char**){
    adder_test test("test");
    sc_start();
    return 0;
}
Link to comment
Share on other sites

One more thing to consider is positional port binding using initializer list.

 

While I never use this feature, I think it can be implemented in some better way.  

Looks like there is a limitation of maximum 64 ports with current sc_module : 

void operator () ( const sc_bind_proxy& p001,
const sc_bind_proxy& p002 = SC_BIND_PROXY_NIL,
const sc_bind_proxy& p003 = SC_BIND_PROXY_NIL,
const sc_bind_proxy& p062 = SC_BIND_PROXY_NIL,
...
const sc_bind_proxy& p063 = SC_BIND_PROXY_NIL,
const sc_bind_proxy& p064 = SC_BIND_PROXY_NIL );

};
Link to comment
Share on other sites

Some syntax to create methods/threads out of constructor body would be helpful too. For example by creating classes sc_thread and sc_method.

 

imagine something like

SC_MODULE (foo) {

SC_CTOR(foo);

// sc_method ( sensitivity_list , process_handle ) 
sc_method  sum_method ( {a,b,c} , [&]() {
         sum = a + b + c;
    } };


void diff_method_body();
sc_method  diff_method ( {x,y} , diff_method_body );

};

void foo::diff_method_body() {
diff = x - y;
}
Link to comment
Share on other sites

One more powerful feature of Verilog are implicit signals and methods.

Consider following example:

logic          clk;
logic [7:0]    addr;
logic          write_en;
logic          slave0_irq;
logic          slave1_irq;

master master0 (
    .clk(clk),
    .addr(addr),
    .write_en(write_en),
    .interrupt_vec( {slave1_irq, slave0_irq} )
);

slave slave0 (
    .clk(clk),
    .addr(addr[6:0]),
    .write_en(write_en && addr[7] == 0 ),
    .interrupt(slave0_irq)
    );

slave slave1 (
    .clk(clk),
    .addr(addr[6:0]),
    .write_en(write_en && addr[7] == 1 ),
    .interrupt(slave1_irq)
    );

In Verilog I can bind port to signal sub-range without explicitly defining additional signal and creating a method to extract sub-range:

 

Verilog:

.addr(addr[6:0]),

SystemC:

sc_signal<sc_bv<7> > subaddr{"subsignal"};
...
void extract_subrange_method() {
    subaddr = addr.read().range(6,0);
}
...
SC_METHOD(extract_subrange_method);
sensitive << addr;
...
slave0.addr(subaddr);

About 7 LOC in SystemC vs 1 LOC in Verilog. 

 

 

Verilog allows to merge multiple signals into one:

.interrupt_vec( {slave1_irq, slave0_irq} )

Or even more complex expressions :

.write_en(write_en && addr[7] == 1 ),

So if I rewrite whole module from Verilog to SystemC I will get about 150-200 LOC vs 35 LOC in original Verilog file. 

 

 

SystemC desperately needs similar syntax if it wants to grow as HDL.  Mixing Verilog and SystemC is not an option: in general I have HLS-style and RTL-style code in same module.   So we either need an HLS for Verilog, or facelift for SystemC. 

 

 

It feels like it is possible to implement such type of binding at library level, without creating additional "SystemC preporcessor".

Because we bind sc_ports to sc_signals (not to primitive types (char, int)) it is possible to improve those classes to support this type of syntax.

 

For example we can apply expression templates over sc_signals:

https://en.wikipedia.org/wiki/Expression_templates

 

So expression like this:

write_en && addr[7] == 1

over sc_signals will return some class sc_expression that will contain both:

  • recursive function to compute expression
  • list of sc_signals that will go into sensitivity list

Next, during port binding create anonymous sc_signal with the same type as port.  And spawn a method that will assign this signal a value computed by expression template.

Link to comment
Share on other sites

  • 3 weeks later...

If we combine expression templates and in-place initialization, enhanced C++11 syntax can look something like that:

SC_MODULE(demo_mod) {

    sc_in<int> a_in{"a_in"};

    // second optional parameter is assignment expression
    sc_out<sc_int<8>> b_out{"b_out", a_in + c_sig};

    // constant zero output
    sc_out<int> zero_out{"zero_out", 0};

    sc_signal<sc_uint<4>> c_sig{"c_sig"};

    // some bus port
    amba::apb_slave apb_in{"apb_in"};

    // sencond optional paramteter to sc_module_name is binding function
    some_module inst_name {{"inst_name", [&]() {
        inst_name.in0(a_in);
        inst_name.apb_in(apb_in); // bus binding
        inst_name.in1(10); // bind to constant

        inst_name.out0(); // unconnected by design
    }}};

    sc_signal<sc_uint<8>> d_sig{"d_sig"};

    SC_CTOR(demo_mod) {
        // assignment
        d_sig.assign( (c_sig , c_sig) - 10 );

        SC_THREAD(test_thread);
    }

    sc_signal<sc_uint<8>> e_sig{"e_sig"};

    void test_thread() {
        int var = 12;
        // wait for expression
        wait( c_sig[0] && (a_in == 12) );

        // evaluates expression
        e_sig = a_in + b_out.range(3,0) + d_sig + 12;

        sc_stop();
    }
};
Link to comment
Share on other sites

 

If we combine expression templates and in-place initialization, enhanced C++11 syntax can look something like that:

SC_MODULE(demo_mod) {

    sc_in<int> a_in{"a_in"};

    // second optional parameter is assignment expression
    sc_out<sc_int<8>> b_out{"b_out", a_in + c_sig};

    // constant zero output
    sc_out<int> zero_out{"zero_out", 0};

    sc_signal<sc_uint<4>> c_sig{"c_sig"};

    // some bus port
    amba::apb_slave apb_in{"apb_in"};

    // sencond optional paramteter to sc_module_name is binding function
    some_module inst_name {{"inst_name", [&]() {
        inst_name.in0(a_in);
        inst_name.apb_in(apb_in); // bus binding
        inst_name.in1(10); // bind to constant

        inst_name.out0(); // unconnected by design
    }}};

    sc_signal<sc_uint<8>> d_sig{"d_sig"};

    SC_CTOR(demo_mod) {
        // assignment
        d_sig.assign( (c_sig , c_sig) - 10 );

        SC_THREAD(test_thread);
    }

    sc_signal<sc_uint<8>> e_sig{"e_sig"};

    void test_thread() {
        int var = 12;
        // wait for expression
        wait( c_sig[0] && (a_in == 12) );

        // evaluates expression
        e_sig = a_in + b_out.range(3,0) + d_sig + 12;

        sc_stop();
    }
};

 

Sir,

Maybe you should create your own proof-pf-concept implementation that

includes all these advanced features that you mention above. You could

then sell it commercially. Maybe you should stop responding to your own 

posts -- justa thought.

Link to comment
Share on other sites

Dear Roman,

 

contrary to the previous poster, I would like to encourage you to keep posting your proposals for improving the syntax of SystemC! They come very timely. The IEEE Std 1666-2011 will need to get revised in the coming years to not become obsolete. The current standard is still based on C++'03 and the next version will need to get aligned to C++'14 or the then current C++ standard version. This forum is the right place for regular SystemC users to expose their ideas about what they would like to see in a revised standards. Members of the SystemC Language Working Group are reading and contributing to the Accellera Forums on a regular basis and are thus aware of your proposals -- even if they don't provide feedback for the moment.

 

Personally, I think that your proposals are a good starting point for thinking further how SystermC users can tear most benefits from the possibilities of the new C++ standards to simplify the code of their models. Improvements to the SystemC syntax, which don't jeopardize backwards compatibility, will be more easy to integrate than proposals, which are not compatible to the current standard. The latter probably need to be very convincing in terms of usability and productivity improvement to get considered in a new revision of SystemC. Personally, I would also try to avoid as much as possible the introduction of any new preprocessor macros in SystemC.

 

So, please keep posting!  :) I hope that also other Forum members will provide feedback in the future.

 

Best regards,

 

Torsten Maehne

Link to comment
Share on other sites

Dear Roman,

 

contrary to the previous poster, I would like to encourage you to keep posting your proposals for improving the syntax of SystemC! They come very timely. The IEEE Std 1666-2011 will need to get revised in the coming years to not become obsolete. The current standard is still based on C++'03 and the next version will need to get aligned to C++'14 or the then current C++ standard version. This forum is the right place for regular SystemC users to expose their ideas about what they would like to see in a revised standards. Members of the SystemC Language Working Group are reading and contributing to the Accellera Forums on a regular basis and are thus aware of your proposals -- even if they don't provide feedback for the moment.

 

Personally, I think that your proposals are a good starting point for thinking further how SystermC users can tear most benefits from the possibilities of the new C++ standards to simplify the code of their models. Improvements to the SystemC syntax, which don't jeopardize backwards compatibility, will be more easy to integrate than proposals, which are not compatible to the current standard. The latter probably need to be very convincing in terms of usability and productivity improvement to get considered in a new revision of SystemC. Personally, I would also try to avoid as much as possible the introduction of any new preprocessor macros in SystemC.

 

So, please keep posting!  smile.png I hope that also other Forum members will provide feedback in the future.

 

Best regards,

 

Torsten Maehne

 

Sir,

While there is nothing wrong about including ever more advanced

C++ language features into SystemC, one has to ponder as to how

these new language features would better facilitate the analysis of

a given design problem. In particular, one has to examine how and

why the Google search engine has maintained its absolute stranglehold,

while Yahoo and Bing have fallen by the wayside. All of the Google

core modules have been written in plain ANSI C.

 

As it is, C++ is a very complicated language compared to its mother

language C, with inheritance, multiple inheritance, function overriding,

operator overloading, pure virtual functions and templates. The only

feature complicated about C is pointer arithmetic, which is carried over

to C++.  In addition, C++ adds its own complexity.

 

As an example, consider the fact that a few years ago intel added

400 extra pins to its 2 core processors, raising the total pin count

from 755 to 1155. However, from personal experience I know that

these 400 extra pins do not in any way improve the performance

of any PC, but rather makes motherboard design more complicated/

difficult and error-prone and prototype debugging/testing a major

headache.

 

Albert Einstein once said something like 'make it simple but not more

simpler', implying that everything should have just the right amount of 

complexity built in, but no less or more.

Link to comment
Share on other sites

 

Maybe you should create your own proof-pf-concept implementation that includes all these advanced features that you mention above

This is my plan.

 

You could then sell it commercially.

My plan is opposite: if this sort of syntax will be helpful for users, commercial vendors will probably support it in existing systemc synthesis tools.

 

Albert Einstein once said something like 'make it simple but not more

simpler', implying that everything should have just the right amount of 

complexity built in, but no less or more.

Speaking about SystemC "language" complexity: this is indeed the case with current SystemC. Because of that most HW designers prefer Verilog/SystemVerilog.

But features I've presented are about reducing SystemC syntax complexity, not about further complication.  So code will be more compact, less verbose and error-prone.

 

Personally I find one good thing about HW design in SystemC: If I will lose my job in semiconductor industry, I can find another one as C++ programmer :)   Something I couldn't do if I were a SystemVerilog programmer.

Link to comment
Share on other sites

This is my plan.

 

My plan is opposite: if this sort of syntax will be helpful for users, commercial vendors will probably support it in existing systemc synthesis tools.

 

Speaking about SystemC "language" complexity: this is indeed the case with current SystemC. Because of that most HW designers prefer Verilog/SystemVerilog.

But features I've presented are about reducing SystemC syntax complexity, not about further complication.  So code will be more compact, less verbose and error-prone.

 

Personally I find one good thing about HW design in SystemC: If I will lose my job in semiconductor industry, I can find another one as C++ programmer smile.png   Something I couldn't do if I were a SystemVerilog programmer.

 

SystemC's predecessor is SpecC, cones with its own compiler and freely downloadable.

Syntax is very much like the plain C language without any of the complexities of the

C++ language. Why SystemC flourished to become an industry standard, and SpeC

remains mostly as an academic tool, is a long story.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...