Ma! Look I designed a counter with C++
To whom is this tutorial directed?
I am a Hardware designer by training that is I know Verilog, VHDL, and even System Verilog. But recently due to wrath of economy, I ended up losing my Hardware Career job & found a position in a company developing system models (ESL) for Virtual Platforms, with hiring company kind enough to let me learn again “on the job”. I don’t get too much time learning, as most of my days are spent in supporting our product teams with issues. So I make some time to teach myself C++ in proper way in my own time. I decided to share my learning with others as well.
This tutorial is dedicated to anyone interested in basic C++ & specifically engineers training themselves in ESL area. I am learning SystemC and C++. Both languages are becoming important for hardware engineers to improve their employability in Architecture, Validation, Emulation, Virtual Platforms roles & even in future for designers. This tutorial is dedicated to all those Hardware Engineers interested in learning C++.
As background, I have worked in various design & validation roles in the past. I am familiar with C language, I have worked with Verilog, VHDL, System Verilog, Specman, Vera, Perl and Python. You can guess – in summary, little bit of all. I call myself a jack who can make one of these languages work for my current project or assignment. So mastering C++ is not one of objectives of this tutorial.
C++ for Hardware Engineers: It’s not hard at all!
C++ is Object-Oriented programming. For Hardware engineers, it should bring comfort to know that OOP was created by borrowing design techniques of HW designs. Data abstraction is central theme of OOP, such as publicly visible “ports” and private “internal signals”, which is common in Block/ Cluster level hierarchy designs.
My Environment:
I work on Windows7 machine. I obtained Visual Studio 2008 & I am going to use it for generating course content. Though for this tutorial, I did not see a need of particular development environment. I have used gcc on unix machine for simple program testing. I have also toyed with Eclipse IDE.
But I don’t want to scare away beginner programmers. So any of the environments is fine, as long as, you can compile code for basic testing of your programs.
My First C++ Program:
#include
using namespace std;
int main ()
{
cout << "Hello World! This is my first C++ Program" <
return 0;
}
Data Types in C++
I won’t spend too much time in data types, you can learn about data-types from several sources. But most important things to learn about C++ is Classes, Objects, etc. So I’ll spend more time on those.
The Class
Keywords/ Terms: Class, Data Member, Method; constructor, destructor
The class in C++ is analogous to package in VHDL. Class defines data members & access functions (methods). Both data members & methods are private by default. In other words, data members and access functions are not visible from the outside world. Lets define a counter in C++ to illustrate classes:
#include
using namespace std;
// Class illustration
class counter
{
int value;
public:
void do_reset() { value = 0 ; }
void do_count_up() { value++ ; }
int do_read() { return value; }
};
int main()
{
counter c1;
c1.do_reset();
for (int i=0; i<40; i++)
{
c1.do_count_up();
}
cout <<"Final counter value is: " << c1.do_read();
return 0;
}
In this example we see the declaration of an CLASS called: “counter” with a data member “value” and publicly available access functions: “do_reset”, “do_count_up” and “do_read”. Although this class declaration is complete; commonly, a class declaration will also contain specialized functions such as constructors and a destructor. When constructors are used, they provide initial values for the data members. This mechanism is the only allowed mean for setting a default value to any data member.
A destructor is used to perform clean-up operations before an instance of the class become out of scope. Pragmatically, the destructor is used for closing previously opened files or de-allocating dynamically allocated memory.
An example of an CLASS with constructors and a destructor is as follows:
#include
using namespace std;
class counter
{
int value;
public:
void do_reset() { value = 0 ; }
void do_count_up() { value++ ; }
int do_read() { return value; }
//constructor
counter() {
cout << "This a simple constructor" << endl;
value = 10;
}
counter(int arg): value(arg) {
cout << "This a more interesting constructor" << endl;
}
~counter() {
cout << "Destroying a counter object" << endl;
}
};
int main()
{
counter c1;
counter c2(33);
for (int i=0; i<40; i++)
{
c1.do_count_up();
}
cout <<"Final counter value w/o reset is: " << c1.do_read() <
c1.do_reset();
for (int i=0; i<40; i++)
{
c1.do_count_up();
}
cout <<"Final counter value w/ reset is: " << c1.do_read()<
cout <<"Remember other counter we created? val= " << c2.do_read()<
return 0;
}
In our example all access functions are made visible to the outside world through the use of the “public” keyword. However, not all functions have to be made public; they can be held hidden from the rest of the world through the use of the “private” keyword.
The Object
The Object
An object is an instance of an CLASS. Any number of instances can be made of a given CLASS; each of these instances are initialized individually.
An example of objects instantiation and messages passing is as follows:
An example of objects instantiation and messages passing is as follows:
void main() {
// Declare instances of classes --> create objects
counter first_counter;
counter second_counter(13);
// Message passing
first_counter.do_reset();
for (int i=0; i < 10; i++) {
first_counter.do_count_up();
}
cout << "First counter value = " << first_counter.do_read() << endl;
second_counter.do_count_up();
cout << "Second counter value = " << second_counter.do_read() << endl;
first_counter.do_reset();
second_counter.do_reset();
cout << "First counter value = " << first_counter.do_read() << endl;
cout << "Second counter value = " << second_counter.do_read() << endl;
}
In this example two instances of the counter CLASS are created. The “first_counter” instance uses the default constructor not requiring any arguments. The “second_counter” instance uses a more sophisticated constructor form that passes an argument.
Messages are send to individual objects via the ‘dot’ notation which is similar to what is used for “struct” data structures.
The building of more complex objects can be achieved through the composition mechanism. This mechanism is akin to component instantiation in Hardware design.
The following example illustrates the principle of hierarchical design through composition.
class modulo_counter
{
counter internal_counter;
int terminal_count;
public:
void do_reset() { internal_counter.do_reset(); }
void do_count_up() {
if ( internal_counter.do_read() < terminal_count ) {
internal_counter.do_count_up();
} else {
cout << "Terminal value reached for Modulo Counter, Resetting..." << endl;
internal_counter.do_reset(); }
}
int do_read() { return internal_counter.do_read(); }
modulo_counter(int tc):
terminal_count(tc),
internal_counter(0) {
cout << "A new modulo_counter instance" << endl; }
};
void main() {
// Declare instances of classes --> creating objects
modulo_counter c1(5);
// Message passing
c1.do_reset();
for (int i=0; i < 50; i++) {
c1.do_count_up();
}
cout << "Counter value = " << c1.do_read() << endl;
In this example a new CLASS “modulo_counter” is created form an instance of a counter CLASS called “internal_counter”. An additional data member “terminal_count” was added to provide the modulo value for this new CLASS. The access function “do_count_up” is customised to take in considerations of the modulo counter specifications. As it can be observed, most actions are delegated to the “internal_counter” instance via message passing. It is interesting to notice how the constructor function is used to initialise both the “terminal_count” value and the instantiated object “internal_counter”. The argument “tc” is used to set the value of “terminal_count” and the value “0” is passed to the constructor of the “internal_counter” object.
Inheritance
Along with composition, C++ provides a sophisticated mechanism for reusing code called: inheritance. With inheritance designers are able to create new CLASSs from existing ones by accessing all public elements of a parent class into a child class. This principle can be more appropriate than composition since it usually requires less effort to achieve the same goal. However inheritance does not replace composition but complements it. The following example illustrates the creation of a modulo counter CLASS from an existing counter CLASS whilst using inheritance.
class modulo_counter : public counter {
int terminal_count;
public:
void do_count_up() {
if ( do_read() < terminal_count ) {
counter::do_count_up(); }
else { do_reset(); }
}
modulo_counter(int tc): terminal_count(tc), counter(0) {
cout << “A new modulo_counter instance” << endl; }
};
In this example a new access function called “do_count_up” is created. This overrides the inherited function from the parent class “counter”. The remaining public access functions (do_reset, do_read) found in the parent class are now available to the child class: “modulo_counter”. As it can be observed the creation of the “modulo_counter” class is greatly simplified through the use of inheritance. Nevertheless, the “do_read” and “counter::do_count_up” functions had to be used in the newly created “do_count_up” function to access the private data members inside the parent class. Although this is a valid solution, C++ offers a more pragmatic alternative, consisting in using a protected encapsulation in place of a private one. Unlike private members, protected members are inherited along with public ones when a parent class is derived, however, protected data members remain hidden from the outside world in the same way as private members do. A new implementation of the counter and modulo_counter class using protected data members is as follows:
class counter
{
protected:
int value;
public:
void do_reset() { value = 0 ; }
void do_count_up() { value++ ; }
int do_read() { return value; }
counter() {
cout << “This a simple constructor” << endl;
value = 10;
}
counter(int arg): value(arg) {
cout << “This a more interesting constructor” << endl;
}
~counter() { cout << “Destroying a counter object”
<< endl;}
};
class modulo_counter : public counter {
protected:
int terminal_count;
public:
void do_count_up() {
if ( value < terminal_count ) { value++; }
else { value = 0; }
}
modulo_counter(int tc): terminal_count(tc), counter(0) {
cout << “A new modulo_counter instance” << endl; }
};
In this last example the data member “value” is directly accessed from within the “modulo_counter” class. The use of the protected encapsulation has simplified the creation of the derived class by making data members from the parent class accessible.
The principle of single inheritance can be extended to allow a child class to be built from multiple parents. This principle is referred as multiple inheritance. Multiple inheritance enables designers to rapidly create new sophisticated classes from a multitude of existing CLASSs. The use of multiple inheritance commonly requires careful planning since it can create numerous undesirable side effects. An example of multiple inheritance is as follows:
The principle of single inheritance can be extended to allow a child class to be built from multiple parents. This principle is referred as multiple inheritance. Multiple inheritance enables designers to rapidly create new sophisticated classes from a multitude of existing CLASSs. The use of multiple inheritance commonly requires careful planning since it can create numerous undesirable side effects. An example of multiple inheritance is as follows:
class reg {
protected:
int value;
public:
void do_reset() { value = 0; }
int do_read() { return value; }
void do_write(int arg) { value = arg; }
reg(): value(0) {}
};
class up_counter: virtual public reg {
public:
void do_count_up() { value++; }
};
class down_counter: virtual public reg {
public:
void do_count_down() { value–; }
};
class up_down_counter: public up_counter, public down_counter { };
In this example the “up_down_counter” CLASS is created from two parent classes: “up_counter” and “down_counter”. The “up_down_counter” does not require any code since it inherits all of its implementation from its parent classes. It is important to point out that both the “up_counter” and “down_conter” are inheriting virtually the register class. The virtual inheritance is used here to prevent multiple declarations of the “value”, “do_reset”, “do_read”, “do_write” inside the “up_down_counter” since this CLASS inherits those attributes twice, though both the “up_counter” and “down_counter” inheritance.
The template mechanism is used for creating more versatile CLASSs. Templates can be used for variable types or values. An example of a simple template class is as follows:
template class barrel_counter {
private:
int value;
public:
void do_reset() { value = min; }
void do_count_up() {
if (value< max ) { value++; }
else {value = min; }
}
int do_read() { return value; }
barrel_counter(): value(min) { }
};
int main(int argc, char *argv[])
{
barrel_counter<10, 50> first_counter;
barrel_counter<0, 10> second_counter;
for (int i=0; i < 60; i++) {
first_counter.do_count_up();
second_counter.do_count_up();
cout << first_counter.do_read() << endl;
cout << second_counter.do_read() << endl;
}
return 0;
}
This counter implementation defines two templates “min” and “max”; these variables are then used inside the class to define the boundaries of the barrel counter. This example uses templates to set variable values; alternatively templates can be used to set a variable type. An example of templates used for variable types is as follows:
template class adder {
private:
T result;
public:
T add( T a, T b ) {
result = a + b ;
return result ;
}
};
int main(int argc, char *argv[]) {
adder integer_adder;
adder float_adder;
int int_result = integer_adder.add(3, 5);
float float_result = float_adder.add(6.7, 10.2);
cout << int_result << endl;
cout <<< endl;
return 0;
}
This example uses a variable type “T” used throughout the class to provide a versatile CLASS implementation. At a later stage two objects of type adder are declared however, both adders use different types for its operations.
Summary
As we illustrated in this section, Hardware components can be modeled in C++ and to some extent the mechanisms used are similar to those used in HDLs. Additionally C++ provides inheritance as a way to complement the composition mechanism and promotes design reuse.
Nevertheless, C++ does not provide for concurrency which is an essential aspect of systems modeling. Furthermore, timing and propagation delays cannot easily expressed in C++. The SystemC library provides additional mechanisms such as processes and dedicated data types to tackle C++ modeling deficiencies.
Nevertheless, C++ does not provide for concurrency which is an essential aspect of systems modeling. Furthermore, timing and propagation delays cannot easily expressed in C++. The SystemC library provides additional mechanisms such as processes and dedicated data types to tackle C++ modeling deficiencies.
No comments:
Post a Comment