Global training solutions for engineers creating the world's electronics products

SystemC Questions and Answers

This page contains questions which have been asked on our SystemC courses. They are grouped into broad categories. Don't forget the OSCI FAQs as well!

C/C++ questions

General SystemC

Fixed Point

Processes

Waveform Tracing

1 Use of namespaces

C++ has a concept of a namespace, which allows you to control the visibility of items. There is a global namespace. This may be accessed by prefixing an object name by :: For instance, I could say

int a;
int main()
{
::a = 10; // :: refers to the global namespace
a = 5; // visible because it's in the global namespace
return (0);
}

Because a is declared in the file before main, it is in the global namespace. In C++, standard headers go into another namespace, called std. When you type #include <iomanip> any items in the standard headers appear in a namespace called std, so you would say #include <iomanip> you would then have to write std::cout << ... This has the advantage that there is now a defined namespace for standard names, and so re-use of names is better defined. The disadvantage is that you have to write std:: all over the place. If you get bored writing std::, then you instead say

// make std directly visible
#include <iostream> using namespace std;

 

and then you can write cout << ... again, because you have explicitly made all the names in std visible. So what's the answer? If you're working on a "proper" large software project, it is clearly best to explicitly refer to the namespace, e.g.

#include <iostream>
int main() {
std::cout << "hallo world\n";

Though clearly it's a bit of a pain!

Return to top

 

2 How does size of work?

If the object is an array, sizeof returns the size of the array, in bytes. If the object is a scalar, sizeof returns the size of the scalar in bytes. When applied to a reference, sizeof returns the size of the object referenced.

Return to top

 

3 Variable Initialization

According to "The Annotated C++ Reference Manual" (Stroustrup/Ellis),

"Variables with storage class static that are not initialized are guaranteed to start off as 0 converted to the appropriate type. So are memebers of static class objects. The initial values of automatic and register variables that are not initialized are undefined"

So for non-static variables the initial values are not defined. However, the operating ystem may clear the memory where the program runs to all zeros. However you should not rely on this.

Note that in SystemC, sc_logic is defined to initialize to 'X'.

Return to top

 

4 What does sc_main do, vs. main()?

sc_main carries out various initialisation functions required for instancing and creating processes.

Return to top

 

5 Is there generally a default value for sc_ types - e.g. does sc_logic default to 'X'? Or is it random?

It seems that the default values are:

for sc_logic: 'X'

for sc_bit: '0'

for sc_bv: 000000..00

for sc_(u)int: +0

Return to top

 

6 How does memory allocated for the instances get freed?

If you allocate memory yourself, then you should write a destructor to delete the memory. If you don't, then the memory should be tidied up when your simulation ends. However, if you were calling SystemC as a program from another simulator (to co-simulate with Verilog for instance), it would be better to write destructors and deallocate memory explicitly.

Return to top

7 Can you pass an argument to SC_METHOD?

No, not for a static SC_METHOD (one declared during construction). However you can have arguments on Dynamic Processes.

Return to top

 

8 Are values updated one clock cycle later?

This used to be true for SC_CTHREADs in Release 1 of the SystemC library. In Release2, SC_CTHREADs behave in the same way as SC_THREADs.

Return to top

 

10 Can wait(N) take a non-static N?

Yes, N can be a constant, or a variable, or even an expression (or a signal!).

Return to top

 

11 What port types does .range() work on?

The range() is only defined for certain types, not on signals or ports. So you may have to put the value of a signal or port into a variable if you want to use the range() method on it. Or alternatively call the read() method first.

Return to top

 

15 Can you show some examples of concatenation?

sc_bv<4> D;
sc_bv<6> E;
sc_bv<3> H;
sc_bv<10> G;
D = "1011";
E = "110010";
H = "001";
G = (D.range(0,3),E.range(5,3), H); // G = "1101110001"

 

 

Return to top

 

16 Can you do reduction on a range? e.g. A.range(7,4).or_reduce()

Yes, you can

sc_lv<6> E;
sc_logic J;
E = "101110";
J = E.range(4,2).xor_reduce(); // J = 0

 

 

Return to top

 

17 Can you use wait(N) in an SC_THREAD?

Yes, this works from in version SystemC 2.2, but not in early versions of SystemC.

Return to top

 

18 How can you hierarchically call signal names? How do you do it?

Using dots between the name of each module instance (if modules are declared without using pointers).

Return to top

 

19 How do you initialise a const?

Constructor arguments can be used in SystemC version 2.0 by using the SC_HAS_PROCESS macro. See the example in the Functional Spec supplied with the SystemC release.

Return to top

 

20 to_string - what does this work on?

It works on fixed point types, as well as on vectors (that is sc_lv)

The value of a fixed point type can be converted to a character string with the to_string() method. This method takes two arguments. The first argument specifies the number representation of the result and the second specifies fixed or scientific format. To specify how a number is represented use the following syntax:

varname.to_string(number representation, format);

 

To print a character string of the value from an sc_lv object use the to_string() method as shown:

sc_lv<32> bus2;
cout << "bus = " << bus2.to_string();

 

 

Return to top

 

21 What is the limit on fixed point mantissa?

There seems to be no limit according to the implementation. Of course the fast versions are limited to 53 bits.

Return to top

 

22 What types do is_zero(), is_neg() work on?

ANSWER: works with fixed point types.

Return to top

 

23 Can you have one bit of a vector in a sensitivity list?

No, this didn't work with v1.0.1

Return to top

 

24 Is connecting ports directly (without an intermediate signal) OK?

Yes, in a hierarchy.

Return to top

 

25 Can you declare sensitivity lists in stages?

Yes, you can write for instance

SC_THREAD...;
sensitive << A;
senstive << B;.

Return to top

 

26 Does the order matter in a sensitivity list?

With a simple example, it seemed to make no difference to speed.

Return to top

 

27 How can you initialise signals of resolved or unresolved types?

See the RELEASENOTES file in the SystemC installation directory.

Return to top

 

28 Does a variable maintain its value across calls to sc_start?

Yes, sc_start behaves like a "continue" rather than a "restart".

Return to top

 

29 What happens if you assign A = "Z" where A has more than one bit?

With sc_lv, the value was left filled with zeros, e.g.

sc_lv<8> A;
A = "Z";   // A contains "0000000Z"

Return to top

 

30 Is there a web-based C++ tutorial?

See http://www.cs.wustl.edu/~schmidt/C++/index.html

Return to top

 

31 Why can't you split a template class into separate files (.h, .cpp)?

Because templates have to be declared in "global scope". Template functions and classes are "instanced" when the type on which the template is defined, which restricts where they can be put in the code.

Return to top

 

32 If you connect two ports directly do you have to have a port map on both instances?

There are two questions here:

a) in a hierarchical module, it is possible to directly connect ports from the higher level to the lower level. In that case, you would have an example like this:

------------------------
|                      |
|      ----------      |
|      |         |     |
|      |         |     |
|      |       P >-----> P1
|      |         |     |
|      ----------      |
|                      |
------------------------

Here port P is connected directly to port P1 via the port map (i.e. without a signal).

b) if you have two blocks "side by side"eg:

------          -------
|    |     S    |     |
|    >---------->     |
|    |          |     |
------          -------

Then S has to be a signal which is wired up into both port maps.

Return to top

 

34 Can you refer to a signal higher up the hierarchy from lower down?

No, I don't think you can, or at least we couldn't find a way of doing it! You can refer to a signal lower down from higher up though...

Return to top

 

35 Could you have a function called from a number of different modules?

Yes, but you would have to be careful to avoid non-deterministic behaviour.

Return to top

 

37 How do floor() and ceil() relate to rounding modes?

floor() is equivalent to SC_RND_MIN_INF

ceil() is equivalent to SC_RND

Return to top

 

38 Does SC_ON, SC_OFF have to be static?

SC_ON and SC_OFF are literals of an enumerated type called sc_switch. So you can declare a variable, e.g.

sc_switch DoCasting;

then enable casting by setting

DoCasting = SC_OFF;

and declare a fixed point value

sc_fixed<16,8> d(DoCasting);

We experimented with making DoCasting a signal, e.g.

sc_signal<sc_switch> DoCasting;

and it compiled and ran, but we didn't actually try to alter the value of the signal during simulation(!). It might work in an SC_METHOD, where the function is dynamically created and destroyed each time it is called, but it would be surprising if it worked in an SC_THREAD or SC_CTHREAD.

Return to top

 

39 Which is fastest out of sc_fix, sc_fixed?

We did some measurements on a Solaris Ultrasparc2 with a FIR filter.

The testbench was modified to run 100000 clock periods. All tests were measured using /usr/bin/time run.x and taking the user time as the figure.

test time
1 45 seconds
1 + all sc_fixed_fast 8.5 s
1 + all sc_fixed_fast except 29 s
fir.cc left as sc_fixed
1 + all sc_fixed_fast, except 31 s
fir.cc using sc_fix
1+ all sc_fixed_fast, except 9.3 s
fir.cc using sc_fix_fast

Comparing 9.3s to 8.5 s, this is about 9% slower.

Comparing 29 s to 31 s, sc_fix was about 7% slower.

Conclusion: The big difference is between sc_fixed and sc_fixed_fast!

Return to top

 

40 Can you choose different architectures like VHDL?

Yes, using preprocessor directives, For example:

//Module1.h
#include "systemc.h"
#define ArchType 3 //2 1
SC_MODULE(Module1)
{
// ports
sc_in<int> A;
....
void do_mod1();
....
}
// //Module1.cc
#include "systemc.h"
#include "Module1.h"
#if ArchType == 1 void Module1::do_mod1()
{
.....
}
#elif ArchType == 2 void Module1::do_mod1()
{
......
}
#else void Module1::do_mod1() {
......
}
#endif

In this way, by setting in the .h file the ArchType to a certain value, you can choose a specific architecture.

Return to top

 

41 Could you put an expression with signals in a port map?

No, because sc_signal class does not accept any operations, such as bitwise, part selection, etc.

Return to top

 

42 Can you drive two parts of a vector from two different processes?

Yes, you can. For example:

//Module1.h
SC_MODULE(Module1)
{
// ports
sc_in<sc_lv<8> > A;
sc_out<sc_lv<8> > C;
......
sc_in<bool> Clock;
.....
sc_lv<5> Vector; //NB: it won't work if you define it as sc_signal<sc_lv<5> > Vector;!
void do_Proc1();
void do_Proc2();
SC_CTOR(Module1)
{
SC_METHOD(do_Proc1);
sensitive << A;
SC_THREAD(do_Proc2);
sensitive_pos << Clock;
}
};
//Module1.cc
#include "systemc.h"
#include "Module1.h"
void Module1::do_Proc1()
{
sc_lv<3> Vector3 = "110";
cout << "Vector3 is =" << Vector3;
.....
Vector.range(2,0) = Vector3;
}
void Module1::do_Proc2() {
sc_lv<2> Vector2 = 10;
while (true) {
wait();
.....
Vector.range(4,3) = Vector2;
cout << "Vector2 is =" << Vector2;
cout << "Vector is =" << Vector;
}
}
Result: Vector = "10110"

Return to top

 

43 Why can't you mix sc_fixed signed and unsigned for bitwise operators?

Probably as a precaution, because bitwise operations treat the sign bit as any other bit..... So, if you could mix them, it might lead to strange situations, where you can't say for sure if the sign bit is the correct one or not.

Return to top

 

46 Can you have more than one trace file?

Yes, simply declare them with different names and make the appropriate calls to sc_create_vcd_trace_file, sc_trace, sc_close_vcd_trace_file.

Return to top

 

47 How does truncation of sc_int, sc_uint work?

Truncation means that when assigning a wide integer into a narrow integer, the most significant bits are removed. For instance, try this program

#include <iostream.h>
#include "systemc.h"
int sc_main(int argc, char* argv[])
{
sc_int<8> Int8;
sc_int<16> Int16, Int16A, Int16B;
Int16A = -1023;
Int16B = 0;
Int8 = Int16A + Int16B;
Int16 = Int16A + Int16B;
cout << "Int16 = " << Int16 << endl;
cout << "Int8 = " << Int8 << endl;
return (0);
}

and you will get the result

-1023
1
-1023 in binary is
1111110000000001

which gets truncated to

000000001

i.e. +1

Return to top

 

48 What happens if I declare a fixed point number with integer word length greater than wordlength?

Try this program and look at the output of Val (the first number which is declared with 2 bits extra in front of the binary point. The output for Val changes with the input as follows

Input 0 2.5 5 7.5 10 12.5 15 17.5 20 22.5
Val 0 0 4 4 8 12 12 16 20 20

i.e. it increases in multiples of 4 because the 2 least significant bits are set to zero.

#define SC_INCLUDE_FX
#include <iostream.h>
#include "systemc.h"
int sc_main(int argc, char* argv[])
{
sc_fixed<4, 6> Val;
sc_fixed<5,3> Val2;
for (int i = 0; i < 10; i++) {
Val = i * 2.5;
cout << "sc_fixed<4,6> = " << Val << endl;
};
for (int i = 0; i < 10; i++) {
Val2 = i * 2.5;
cout << "sc_fixed<5,3> = " << Val2 << endl;
};
return (0);
}

Return to top

 

49 Can you trace sc_fixed?

Yes, in gtkwave they are displayed as vectors.

Return to top

 

51 Can you use the iostream manipulators (e.g. setw(n) )?

These work OK with V2.0 and UNIX versions of V1.2.1beta - you needto include <iomanip> but not <iostream> since this is implicitly included from "systemc.h".

Return to top

 

52 Why do some type conversions require assignments to interim variables?

C++ only allows a single type conversion as part of an assignment statement. If an explicit type conversion is not defined for particular expression, you sometimes need to do the assignment in 2 steps.

Return to top

 

53 Why can't I have a port called OUT? (Microsoft VC++6)

OUT is defined in windefs.h, which is included in windows.h, which is included in sc_main.h

Other macros include IN, MAX_PATH, OPTIONAL

If you create, for instance, a port called "OPTIONAL" it also doesn't work. Therefore you should avoid the names defined in windefs.h

Return to top

 

54 Can you concatenate (vector, scalar), as well as (scalar, vector)?

Yes, this bit of code works

void alu::do_FReg() {

sc_lv<7> Ftmp;
sc_logic Carrybit;

while (true) {
wait();
Ftmp = FRegInt.read().range(6,0);
Carrybit = (sc_logic)'1';
F = (Ftmp, Carrybit);
}
}

and it doesn't matter if you concatenate (Ftmp, Carrybit) or (Carrybit, Ftmp) (though of course they are functionally wrong!).

Return to top

 

57 Can you assign integers with a quoted string, e.g.

A = "0010";

or

A.write("0010");

for a signal?

This didn't work for an sc_int<4> variable - but *did* work if you cast it to sc_lv<4> first!

e.g. A = (<sc_lv<4>)"0010";

where A is an sc_int<4>.

Return to top

 

58 Can the type casting (SC_OFF) be set dynamically for fixed-point types?

Yes it can, provided a simulation context is set before any fixed-point objects are constructed. The procedure to define a context is as follows:

1) Declare a type cast switch object with the desired parameters, e.g.

sc_fxcast_switch sw1(SC_OFF);

2) Use this to create a context, e.g.

sc_fxcast_context c2(sw1);

Unfortunately, this context only has local scope. If a design includes hierarchy, a new context must be created as part of every sc_module and every member function that reads or makes assignments to fixed-point values. For modules, the easiest way to create the appropriate context is to pass the required value of the switch into the module constructor. Member functions can create a new context by calling the value() method of the module context.

A similar approach is required to set the parameters of sc_fix types.

Here is a modified part of an FIR filter example from our course to show some of these features:

//main.cpp
...
int sc_main(int argc, char* argv[])
{
// get wl, iwl and fmode from keyboard
int wl, iwl;
char fcsw;
cout << "Enter wl and iwl: ";
cin >> wl >> iwl;
cout << "Fixed point on? (enter y or n): ";
cin >> fcsw;
sc_switch fmode;
fmode = (fcsw == 'y') ? SC_ON : SC_OFF;

//create contexts
sc_fxtype_params myparams (wl,iwl);
sc_fxcast_switch myswitch(fmode);
sc_fxtype_context c1(myparams);
sc_fxcast_context c2(myswitch);
...

// component instances
fir DUT("FIR",c1,c2);
DUT.DIn(DIn);
DUT.DOut(DOut);
DUT.Clock(Clock);

...

// ex15/fir.h
//

SC_MODULE(fir)
{
// declare contexts
sc_fxtype_context c_1;
sc_fxcast_context c_2;

// ports
sc_in<sc_fix > DIn;
sc_out<sc_fix > DOut;
sc_in<bool> Clock;

// variable to store 8 bits of data
sc_fix SR[11];

void do_fir();
SC_HAS_PROCESS(fir);

//constructor
fir(sc_module_name nm,sc_fxtype_params p1, sc_fxcast_switch p2)
:c_1(p1),c_2(p2), sc_module(nm)

{
SC_THREAD(do_fir);
sensitive_pos << Clock;

}
};

void fir::do_fir()
{
sc_fxtype_context c_local(c_1.value());
sc_fxcast_context c_local2(c_2.value());

static const sc_fix Coeffs[] =
{ -0.043920827,
-0.051494245,
-0.034401122,
0.013762074,
...
}
}

 

Return to top

 

59 Can a process identify which event has triggered it?

Only by testing the event() method of signals in the static sensitivity list, e.g.

void Exec() {
...
if (Reset.event()) { ... //do reset actions }
else { ... //do other actions }
...
};

There is no mechanism in V2.0 to determine which event has caused a process with dynamic sensitivity to be triggered (see SystemC discussion forum - Stuart Swan, Sept 21, 2001).

Return to top

 

60 How can you split a vector into individual elements in gtkwave?

Gtkwave can be associated with a file containing setup parameters. On a pc, create a file called gtkwave.ini in the same directory as the vcd file for these parameters. Make sure this file contains the command:

atomic_vectors no.

You should then be able to split up vectors in the waveform display.

You can group the individual bits back together using Combine-Up or Combine-Down, and then use Alias to rename the vector.

Return to top

 

62 What is the definition of "this"?

Here is what it says in the C++ Standard (ISO14882:1998).

"9.3.2 The "this" pointer

In the body of a nonstatic member function, the keyword this is a non-lvalue expression whose value is the address of the object for which the function is called. The type of this in a member function of a class X is X*."

In other words, it does contain the address of the class instance, and is of type "pointer to X" inside an instance of class X.

Return to top

 

63 What happens if you leave the & out of a copy constructor?

Here is an example of a copy constructor for a simple RAM model.

//copy constructor
RAM::RAM (const RAM &copy)

If you leave out the &, on gcc it won't compile. It's not clear in the ISO standard why not, but it is explained in "The Annotated C Reference Manual" by Ellis and Stroustrup, ISBN 0-201-51459-1.

The problem is that when you pass an argument by value, e.g. by declaring the copy constructor without use of a reference

RAM(RAM )

the argument has to be constructed, i.e. the copy constructor is called. This results in recursion, since the copy constructor itself has an argument which is passed by value. The example given in Stroustrup and Ellis is:

Suppose class X has a copy constructor declared as

X(X);

then

X a; // an instance of X

extern f(X); // illegal, but there to demonstrate the problem

void g()

{

f(a); // means f(X(a)), i.e. f(X(X(a))) i.e. f(X(X(X(a)))) ....

}

so you get recursion. Rather than design the language to cope with this, the restriction is made that the copy constructor argument must be passed as a reference.

The operator= *can* be declared using pass by value, e.g.

const RAM & operator=(const RAM)

instead of

const RAM & operator=(const RAM &)

this works fine. However it's inefficient, as it means the copy constructor will be called every time you do an assignment to create the temporary argument passed by value. Generally, it's better to use references as it's much more efficient. In fact you could argue that pass-by-value is only there to support backwards compatibility with C - modern languages such as Java *only* use pass-by-reference.

Return to top

 

63 Is there a difference between type and non-type template arguments?

There's no essential difference between type and non-type template arguments. You can declare a template class with a mixture, e.g.

template <class Type1, int E1, class Type2>
class MyClass
{
//...
};


and then instantiate it with

MyClass<double, 10, int> thing1;
MyClass<int, 20, float> thing2;


You must get the types and values etc. in the correct order and matching. A template class declaration has to be in file scope (not in a function).

Return to top

64. Does new initialise integers to 0?

Stroustrup and Ellis says that it does not. The full syntax of new is:

new [placement] newtypename [new initialiser]

So you can say

int * mynum = int(10);

and mynum will be a pointer to int, and the location pointed to will be initialised to 10.

The book then says:

"Note that memory returned by operator new() need not be initialised. In this, it resembles memory allocated on the stack. Only static memory has a default initial value, zero"

Return to top

65. Why are operators often declared as friends?

Again, the answer lies in Stroustrup and Ellis, page 249. It is of course possible to declare operators which are not friends. They will work on ojects of that class. So for instance, if a class defines a + operator, you can say

Myclass a,b,c;
c = a + b;

and there's no problem. However a friend function allows user-defined conversions to be used for its first argument, whereas member functions do not. This allows a programmer to make a distinction between operators where an lvalue is required, and operations where an lvalue is not. Here's an example from Stroustrup and Ellis

class complex {
// ...
complex operator+=(complex);
friend complex operator+(complex, complex);
};

If the function is a friend, then an lvalue is not required. I.e. I can write

1+z;

without assigning it to anything. But I am not allowed to write

1+=z;

because I declared operator+= as a member, and therefore the value on the left must be of type complex, not just the number 1 as in the example.

Return to top

66. What exactly can be accessed using "friend"?

A function declared as a friend is *not* a member of that class, but is allowed to access private members of that class.

Return to top

67. Can you drive/create a global signal?

We tried this, and it worked. Using Exercise 1 from the SystemC Fundamentals course.

Declare a signal *in front* of sc_main, e.g.

sc_signal<bool> Global;
sc_main(...

We then declared the signal as extern, e.g.

#ifndef MULT_H
#define MULT_H
#include "systemc.h"
// SystemC 2.0
// ex1/mult.h
//
...

extern sc_signal<bool> Global;
SC_MODULE(mult)

I also made it visible in the monitor module using

// SystemC 2.0
// ex1/mon.h
//

extern sc_signal<bool> Global;
...

and it could then be driven in mult.cpp and read in mon.cpp.

Return to top

 

 

68. Can you use a static variable to remember values across calls to a function registered as an SC_METHOD?

Yes and No. Yes in the sense it will compile: however the problem is that the static variable will be shared across all instances of a class - which is a bad thing.

Return to top

69. Can an SC_METHOD or SC_THREAD have a return type?

No.

Return to top

70. Is there a workaround to allow a mixture of & and | when using the wait() statement to wait on events?
Not as far as we are aware. Events may only be combined with & or | in a particular wait.

Return to top

71. How does the built-in SystemC assert work?

sc_assert(expr)

where expr is an expression. If the expression evaluates to false, a message is written out, including the file and location where the assertion occurred. Here's some example output:
SystemC: comparator.cpp:13: sorter.C3.do_comparator:10: Assertion `O0.read()==1' failed.

Return to top

72. Can waveform tracing be disabled?

No, not as far as we can find out.

Return to top

73. Can waveform tracing be started during a simulation?

Yes, by making more than one call to sc_start(), e.g.

sc_start(100, SC_NS);

// now declare a new trace file and register things to trace
sc_start(200, SC_NS); // new trace file now operates

Return to top

74. Can you trace private data?

No, not from outside a class. However, if you write a member function of that class, the private data member can then be registered using sc_trace, and can then be traced.

Return to top

75. Is there a limit on the number of waveforms traced in a vcd file?

In the Verilog standard (section 18, where VCD is defined), it just says that the identifier consists of ASCII characters, so there's no apparent limit to the size of the identifiers, and hence to the number of waveforms.

Elsewhere in the standard, it says that Verilog implementations must support identifiers (in Verilog code) at least 1024 characters long, but it's not clear if that applies to VCD.

Return to top

76. What is the internal representation of fixed_fast types? Can values more than 53 bits be stored internally?

If you look at the code, the fixed fast types are stored as type double. The number "53" is true if double is implemented as an IEEE P754 floating point implementation. So you can do whatever double supports on your computer. In IEEE floating point, the mantissa is limited to 53 bits, and the exponent to between -1024 and 1023.

The fast fixed point types still have the overflow and rounding flags available, so you can still check for overflow and loss of precision by using e.g.

sc_fixed_fast<100,100> X;

X = 100*100
if (X.overflow_flag())
cout "it overflowed" << endl;

Return to top

77. Do you get an error if you exceed 53 bits when using fixed_fast types?

No you don't. In fact you can represent powers of 2 greater than 52 bits as the exponent can go up to 1023 on most machines (as described above).

On interesting side effect of all this is that the fast fixed point types are dependent on the implementation of the double type.