PandA-2024.02
Circuit Representation

This page is an introduction to the class structural_manager that is used to create or explore an abstract representation of a structural circuit. The page is organized as follow:

An overview about structural representation of a generic circuit

The structural_manager class allows to create or explore a generic and hierarcally circuit representation. This kind of circuit is composed by structural objects (see Structural object) that can be used to represent the functionality. Objects can represent items at different level of abstraction: logic, RTL and TLM, it is used to mask different kinds of objects and it can contains more structural objects itself. For example, if you want to create a structural_object that perform operation A + ( B * C), you can create a structural_object that will contains structural_object that perform mult between B * C and then the structural_object that that will perform the addition between A and the result of mult and then connect them together. A structural_object can be also used to mask the implementation of a functionality. You can create a module to perform communication with inputs and outputs defined and connected outside and, only after, this object is specialized, for instance with a technology builtin component. Now we shortly describe this structural_object component and then we could see how structural_manager class can use these objects. Before doing this, an important component has to be introduced: the structural_type_descriptor (see structural_type_description). It is used to identify a particular kind of component and all components with the same type descriptor will be considered in the same way. For example if you declare two modules A and B with the same type descriptor, it simply means that are two different instances of the same object, so when you specialize object, both modules will be affected by this modification. If instead you declare them with different type descriptors, they can be specialized in different ways. So there will be a documentation about:

Structural type descriptor

This structure represents the most relevant information about the type of a structural object. In case the structural object is a signal or a port all member has a real meaning (type of the carried data), while when the structural object is a channel, component, event, data or an action the only relevant information stored into the descriptor is the id_type and treenode when available. You can create the structural_type_descriptor associated to a variable, and it returns the type descriptor associated to it (so into field size you can retrive information about real variable size); to create it you have to declare it as:

where id is the identifier (unsigned int) associated to the variable and TM is the reference to tree manager. After that data_type will contain the type descriptor associated to the variable. So, for example with

unsigned int dim = data_type->size;

you can store into variable dim the size of the variable identified by id. For a standard type you simple declare it as:

for and 32-bit integer variable ("bool" type for instance is widely used) and 0 number represents the vector size. In case vector_size is zero the descriptor type represents a scalar object, otherwise an array. If you want to declare a module, you have to declare it as:

where adder is a name chosen for this specific kind of object. Once you declared all needed type descriptor you are able to create any kind of structural object. To have more details, see structural_type_descriptor class documentation.

Structural object

A structural_object class is a base object for all objects. It provides a common interface for each structural object present in a design. So it contains informations about name, type descriptor associated with the object and the owner of the object (informations common to all kind of objects). A structural_object can be specialized into different kind of object that are:

Module

This class describes a generic module. A module can be futher specialized in a channel or in a component. This class should be not instantiated. Only channels and components can be instantiated. To obtain reference to this kind of object, having the structural object associated, just:

module *desidered_module;
desidered_module = get_pointer<module>(object);

By this way, you try to cast structural_objectRef object to module type. If object is really a module object, into desidered_module will be pointer to it. So you can access to specific method implemented for module objects, such as:

To obtain more details or descriptions of other methods, see module class documentation.

Component

This class describes a generic component and it is a specialization of module object. To obtain reference to this kind of object, having the structural object associated, just:

component_o *desidered_component;
desidered_component = get_pointer<component_o>(object);

By this way, you try to cast structural_objectRef object to component type. If object is really a component object, into desidered_component will be pointer to it. So you can access to specific method implemented for component objects. To get informations about them, see Module documentation or see component_o class documentation.

Channel

This class describes a generic channel and it is a specialization of module object. To obtain reference to this kind of object, having the structural object associated, just:

channel_o *desidered_channel;
desidered_channel = get_pointer<channel_o>(object);

By this way, you try to cast structural_objectRef object to channel type. If object is really a channel object, into desidered_channel will be pointer to it. So you can access to specific method implemented for channel objects. To get informations about them, see Module documentation or see channel_o class documentation.

Port

This kind of object is called port_o and it describes a port associated with a component or a channel. A port can be in relation with:

A fundamental information associated with port is direction. It can be IN (input port), OUT (output port), IO (input/output port), GEN () and UNKNOWN (not specified). To obtain reference to this kind of object, having the structural object associated, just:

port_o *desidered_port;
desidered_port = get_pointer<port_o>(object);

By this way, you try to cast structural_objectRef object to port_o type. If object is really a port_o object, into desidered_port will be pointer to it. So you can access to specific method implemented for port objects, such as:

To obtain more details or descriptions of other methods, see port_o class documentation.

Port Vector

This class describes a vector of ports associated with a component or a channel. A port_vector can be in relation with:

The port vector is simply a vector of port_o object. There are only additional method to deal with multiple objects. So, to obtain reference to this kind of object, having the structural object associated, just:

port_vector_o *desidered_port_vectors;
desidered_port_vector = get_pointer<port_vector_o>(object);

By this way, you try to cast structural_objectRef object to port_vector_o type. If object is really a port_vector_o object, into desidered_port_vector will be pointer to it. So you can access to specific method implemented for port vector objects, such as:

To obtain more details or descriptions of other methods, see port_vector_o class documentation.

Bus connection

This class describes a generic bus connection. A bus_connection_o is a a vector of signals or channels [1..n]. To obtain reference to this kind of object, having the structural object associated, just:

bus_connection_o *desidered_bus;
desidered_bus = get_pointer<bus_connection_o>(object);

By this way, you try to cast structural_objectRef object to bus connection type. If object is really a bus connection object, into desidered_bus will be pointer to it. So you can access to specific method implemented for bus connection objects. To get informations about them, see bus_connection_o class documentation.

Event

This class describes a generic event. To obtain reference to this kind of object, having the structural object associated, just:

event_o *desidered_event;
desidered_event = get_pointer<event_o>(object);

By this way, you try to cast structural_objectRef object to event type. If object is really a event object, into desidered_event will be pointer to it. So you can access to specific method implemented for event objects. To get informations about them, see event_o class documentation.

Data

This class describes a generic data declaration object. To obtain reference to this kind of object, having the structural object associated, just:

data_o *desidered_data;
desidered_data = get_pointer<data_o>(object);

By this way, you try to cast structural_objectRef object to data type. If object is really a data object, into desidered_data will be pointer to it. So you can access to specific method implemented for data objects. To get informations about them, see data_o class documentation.

Action

This class describes a generic systemC action. An action can be a SystemC process or a standard service (aka a member function of a class) You can associate the graph_manager reference associated to this action (e.g. the graph_manager representing the function associated to action). To obtain reference to this kind of object, having the structural object associated, just:

action_o *desidered_action;
desidered_action = get_pointer<action_o>(object);

By this way, you try to cast structural_objectRef object to action type. If object is really a action object, into desidered_action will be pointer to it. So you can access to specific method implemented for action objects. To get informations about them, see action_o class documentation.

Signal

This class describes a generic data declaration object. This class describes a simple logic/RTL signal. A signal can be an array of bit but it cannot be sliced or partially accessed. In case of partial access or slicing the bus_connection_o object should be used. To obtain reference to this kind of object, having the structural object associated, just:

signal_o *desidered_signal;
desidered_signal = get_pointer<signal_o>(object);

By this way, you try to cast structural_objectRef object to signal type. If object is really a signal object, into desidered_signal will be pointer to it. So you can access to specific method implemented for signal objects. Using these methods you can get informations about ports bounded to this signal object. To get informations about them, see signal_o class documentation.

Technology library and parameter specialization

Some components can be defined as parameters. This is useful when you could have the same module that can be declared in a different way, for instance, with a different number of inputs. This situation can happen quite often: think at a AND gate that can have a different number of input. Cannot be declared all possible different implementation, so the component can be declared with some parameters. Into technology_builtin.hpp file are declared all builtin functional unit. For example, there is the line:

#define AND_GATE_STD "AND_GATE"

it means that you try to add a component from technology library that match AND_GATE_STD (so "AND_GATE" string), there will be added a component defined so (into technology_builtin.cpp):

//AND
2: fu_name = AND_GATE_STD;
4: CM->set_top_info(fu_name, module_type);
5: top = CM->get_circ();
6: CM->add_port_vector("in",port_o::IN,port_vector_o::PARAMETRIC_PORT,top,b_type);
7: CM->add_port("out1",port_o::OUT,top,b_type);
8: NP_parameters = fu_name + " in";
9: CM->add_NP_functionality(top, NP_functionality::LIBRARY, NP_parameters);
10: TM->fill_resource(fu_name, fu_name, CM, LIBRARY_STD, 0, 0, 0.1, 0.1);
11: builtin_verilog_gates::add_builtin(AND_GATE_STD, "and");

Let analyse each row in order to understand a own parametric component.

  1. A structural_manager CM is created to be associated to component
  2. the name of the unit is that defined above into header file.
  3. a structural_type_descriptor is created to be associated to this functional unit.
  4. the top info of the circuit in creation with unit name and type descriptor
  5. get the top of the circuit in order to add or retrive components
  6. add a port vector as input to module. Note the parameter port_vector_o::PARAMETRIC_PORT that explain to structural_manager that parameters will have to be specified.
  7. add a usual output port.
  8. define the string NP_functionality composed by name of the unit and then name of parameters. The parameters are identified by id of the components are associated with. So in this case, the parameter is the name of the incoming port. If there are not any parameter, the NP_functionality string will contain only the unit name.
  9. the NP_functionality is added to the component.
  10. unit is added to a specific technology library (into LIBRARY_STD library in this case). A set of values are then specified; there are, in order, execution_time, initialization time, power consumption and area.
  11. add gate also to builtin verilog gates

When you will need to create an instance of these variable, you will have to specialize parameters listed into line (8); in this case, for instance, when you add module, you will have to do something similar to this:

// where TM is the technology_manager where have be stored information about modules listed above
obj = structManager->add_module_from_technology("AND_GATE_1",AND_GATE_STD,LIBRARY_STD,owner,TM);
structural_objectRef port_obj = obj->find_member("in",port_o_K,obj);
port_o *in_port = get_pointer<port_o>(port_obj);
// where num is number of ports that you need
in_port->add_n_ports(num,port_obj);

So parameters has been specialized (it can specialized only once for each instance, e.g. in this case you cannot specialize obj anymore). Now let consider a systemC implementation of this component in order to understand what we have done. It will be something similar to:

#define AND_GATE_DECL(size_in_)\
class AND_GATE_##size_in_ : public sc_module \
{\
public:\
sc_in< bool > in[size_in_];\
sc_out< bool > out1;\
\
SC_CTOR(AND_GATE_##size_in_) //constructor is parametric to size, e.g. parameter just set\
{\
SC_METHOD(action);\
for(int i = 0; i< size_in_; i++)\
sensitive << in[i]; //sensitive to each bit change\
}\
void action()\
{\
bool value = true;\
for (int i = 0; i < size_in_; i++)\
value = value & in[i].read();\
out1.write(value);\
}\
}
#ifdef SCC_SYNTHESIS_DEBUG
AND_GATE_DECL(1); //it's default value
#endif

So when backend will create a component where parameters will be set to num = 3, the macro

AND_GATE_DECL(3);

will be expand by preprocessor into something similar to this:

class AND_GATE_3 : public sc_module
{
public:
sc_in< bool > in[3];
sc_out< bool > out1;
SC_CTOR(AND_GATE_3) //constructor is customized to size 3
{
SC_METHOD(action);
for(int i = 0; i < 3; i++)
sensitive << in[i]; //sensitive to each bit change
}
void action()
{
bool value = true;
for (int i = 0; i < 3; i++)
value = value & in[i].read();
out1.write(value);
}
}

that is the implementation of a 3-input AND_GATE. So we have seen how to implement a parametric port vector and how to use it. It can be done something similar also for port size. You have to declare the port in the usual way, but the name has to be listed into NP_functionality string:

// think this unit called TRY_GATE
CM->add_port("in",port_o::IN,top,int_type);
CM->add_port("out1",port_o::OUT,top,int_type);
NP_parameters = fu_name + " in";

In this case only input port in is listed as a parameters. So size can be changed on fly during computation. Once you declared port, you can define a size different from 32-bit usual integer one.

// where TM is the technology_manager where have be stored information about modules listed above
obj = structManager->add_module_from_technology("GATE",TRY_GATE,LIBRARY_STD,owner,TM);
structural_objectRef port_obj = obj->find_member("in",port_o_K,obj);
port_o *in_port = get_pointer<port_o>(obj);
// where num is number of ports that you need
in_port->set_port_size(num);

where num is the new port size (in bit). The systemC module will be something similar to which seen for AND_GATE, with a define like these:

#define TRY_GATE(size_in_) \
class TRY_GATE_##size_in_ : public sc_module \
....
sc_in< sc_int<#size_in_> > in;
...

So you are now able to create you own functional unit and relative modules. Then you are able to specify them and create relative code to be used by you favourite backend writer.

Brief tutorial on the creation of a new generic structural representation

Now that all basic components have been described, they can be used to create the generic representation of the circuit. In this section I plan to present a brief tutorial on how to build a new circuit using the API of the structural_manager class.

A circuit is internally represented by a graph (which can be accessed with the CGetOpGraph method of the structural manager class), but it can be built using the methods of the structural_manager class. The rest of this section will be devoted to the description of the behavior of this class using also some pratical examples.

As you can see, creating a circuit with the API of the structural_manager class is a pretty easy operation; I will anyway present now a conclusive example which shows how to create a circuit with 2 components: an OR gate (taken from the component library) and custom gate which in turn is formed by an AND gate and a NOR one (again taken from the component library); the overall functionality of the circuit (given that A, B, C are its primary inputs and D the output) is D = !(AB) + C

//... at this point of the code consider to have TM reference to technology_manager class.
//I get a reference to an instance of the structural_manager class
//I set all the propeties of the main circuit
circuit_type = structural_type_descriptorRef(new structural_type_descriptor("MainCircType"));
structManager->set_top_info("MainCircuit",circuit_type);
//Now I read the reference to the object which represents the main circuit
structural_objectRef circuit = structManager->get_circ();
//I add the primary inputs/outputs to the top level component
structural_objectRef portA = structManager->add_port("portA", port_o::IN, circuit, bool_type);
structural_objectRef portB = structManager->add_port("portB", port_o::IN, circuit, bool_type);
structural_objectRef portC = structManager->add_port("portC", port_o::IN, circuit, bool_type);
structural_objectRef portD = structManager->add_port("portD", port_o::IN, circuit, bool_type);
//I create the NAND custom component and its primary inputs/outputs; note that now
//the owner of the ports is not the top component anymore, but the NANDComp one.
structural_objectRef customComp = structManager->create("Internal1", component_o_K, circuit, NAND_type);
structural_objectRef portInNAND1 = structManager->add_port("in1", IN_PORT, NANDComp, bool_type);
structural_objectRef portInNAND2 = structManager->add_port("in2", IN_PORT, NANDComp, bool_type);
structural_objectRef portOutNAND = structManager->add_port("out", OUT_PORT, NANDComp, bool_type);
structManager->add_connection(portA,portInNAND1);
structManager->add_connection(portB,portInNAND2);
//the OR is present in the technology library
ORComp = structManager->add_module_from_technology_library("ORInstance",OR_GATE_STD,LIBRARY_STD,circuit,TM);
structural_objectRef portIn1OR = ORComp->find_member("in1",port_o_K,ORComp);
structural_objectRef portIn2OR = ORComp->find_member("in2",port_o_K,ORComp);
structural_objectRef portOutOR = ORComp->find_member("out1",port_o_K,ORComp);
//Finally we have to define the behavior of the custom NAND component: note that,
//of course, the owner of the component we will add now is NANDComp
ANDComp = structManager->add_module_from_technology_library("ANDinstance",AND_GATE_STD,LIBRARY_STD,customComp,TM);
structural_objectRef portIn1AND = ANDComp->find_member("in1",port_o_K,ANDComp);
structural_objectRef portIn2AND = ANDComp->find_member("in2",port_o_K,ANDComp);
structural_objectRef portOutAND = ANDComp->find_member("out1",port_o_K,ANDComp);
structManager->add_connection(portInNAND1,portIn1AND);
structManager->add_connection(portInNAND2,portIn2AND);
NOTComp = structManager->add_module_from_technology_library("NOTinstance",NOT_GATE_STD,LIBRARY_STD,customComp,TM);
structural_objectRef portIn1NOT = NOTComp->find_member("in1",port_o_K,NOTComp);
structural_objectRef portOutNOT = NOTComp->find_member("ou1t",port_o_K,NOTComp);
structManager->add_connection(portOutAND,portIn1NOT);
// and finally connect out of NOT gate to out of NAND component
structManager->add_connection(portOutNOT,portOutNAND);
//so NAND component is complete and can be connected to OR gate
structManager->add_connection(portOutNAND,portIn1OR);
// portC is other input to OR gate
structManager->add_connection(portC,portIn2OR);
//Up to now can connect OR gate to output
structManager->add_connection(portOutOR,portD);

If you then need to print the SystemC description of the just created, you have only to call the backend and give the dedicated writer as parameter (see SystemC, VHDL, Verilog backends to more details)

Note
this description is not complete and considers only the most important features and functionalities. To obtaint more informations, please refer directly to classes documentation.

Brief tutorial on the exploration of a generic structural representation

Now that you know how to create a structural representation, you could be interested into explore it or to retrieve information about one that has already been created (e.g.: with tree_to_structural_manager class). As explained above, structural objects allow a hierarcally organization. Description of methods can be retrived into structural_manager class documentation, so the better way to understand how to explore the circuit is to give an example. Now we want to explore and print component name of the circuit just created. So we have the structural_manager structManager resulting from adding and connecting components. First of all we have to get the structural_object associated to main circuit:

structural_objectRef explorer = structManager->get_circ();
module *top = get_pointer<module>(explorer);

We have to recast them to module class in order to use specific methods implemented for this kind of object. Then we have to get the number of all internal components:

unsigned int num_component = top->get_internal_objects_size();

Take into account that internal object are only channel, module, component, bus_connection or signal. Ports are not considered internal object and so they are managed into a different way:

unsigned int num_in_port = top->get_in_port_size();
unsigned int num_out_port = top->get_out_port_size();

In this way we can retrieve informations about number of all internal components. So we can simple iterate all over them:

for (unsigned int n = 0; n < num_in_port; n++)
{
// so we have a reference to structural object associated with port
// and then we cast it to specific type
port_o *port_ref = get_pointer<port_o>(obj);
// so we can print informations about name and number of connected objects for example
// name can be retrieved from port_o object or structural_object both because is a general method
std::cout << "Name: " << port_ref->get_id() << " that is the same of " << obj->get_id() << std::endl;
std::cout << "Number of connected objects: " << port->get_connections_size() << std::endl;
}

For outcoming ports is the same. It's quite different about internal objects, because they can also contains internal objects themselves. You have to choose the depth you want to go on. The better way to analyse all internal objects of a component is to call a recursive function on all internal objects and it returns only when get_internal_objecs_size of the component return 0 (there is no internal component). Now I give a simple example about a recursive function, you can use and customize it in order to obtain what you are interested in:

void analyse_component(structural_objectRef &obj)
{
module *module_obj = get_pointer<module>(obj);
// check if it's really return a valid pointer to an module object
if (module_obj)
{
// print name of this module
std::cout << module_obj->get_id() << std::endl;
unsigned int num_int_components;
for (unsigned int n = 0; n < num_int_components; n++)
{
analyse_component(module_obj->get_internal_object(n));
}
}
}

Where now it prints name of the module, you can do anything you need on this module before going into each internal component. Note the check if (module_obj): it's used to be safe that object is really a module, otherwise specific methods (e.g. get_internal_object one) cannot be used.


Generated on Mon Feb 12 2024 13:03:40 for PandA-2024.02 by doxygen 1.8.13