Now that we have provided a means by which functional units of the circuit may be created, we next must devise a strategy by which these units may communicate with one another and with their external inputs and outputs. As alluded to earlier, communication between components will be handled by the Port and Wire classes. However, instead of simply creating these classes independently from one another, we note that both these classes have a common underlying theme; namely, they are responsible for connecting entities together. Hence, it is natural to think of ports and wires as being kind of connectors, since ports connect components to the external world and wires connect two or more components together. Therefore, we create a common base class called Connector from which we subsequently derive the Port and Wire classes. These derivations are illustrated in Figure 4.7.
Figure 4.7: Connector Class Diagram
The Connector class contains two virtual member functions, get_signal() and send_signal(). These methods are responsible for obtaining and transmitting signals respectively, hence providing the necessary facilities for inter- and intra-component signal flow. Note that the Connector class itself does not actually define these methods. Instead, it relies upon the specialized classes derived from it to actually implement the semantics of the two methods. Virtual functions which cannot be implemented by a base class due to its high level of abstraction are commonly referred to as pure virtual functions. Because the Connector class contains pure virtual functions, no objects can be instantiated from this class. A class which cannot have instances is called an abstract base class. As can be seen from the diagram, abstract base class icons are adorned with the letter A enclosed in an inverted triangle. Objects may be instantiated from classes derived from an abstract base class provided that the derived class defines all the pure virtual functions of its base class.
To the left of Connector in the figure is the fan_out class. Because connectors are responsible for connecting components together, they must maintain a list of all the components to which they are connected for the purposes of signal propagation and vertical hierarchy traversal. The fan_out class is associated with the Connector class by a physical aggregation relationship -- each Connector class physically contains a list of pointers to components. The fan_out class acquires its list handling capabilities by instantiating from the List parameterized class and by using the Component class. The propagate() member function of the Connector class traverses the components in the fan-out list and sends Component::simulate() messages to each of them.
The Wire class, as mentioned earlier, is derived from the Connector class. It physically contains a signals list which is instantiated from the List parameterized class using the Signal class. The Wire class overrides the get_signal() and send_signal() methods. The get_signal() method simply traverses the wire's linked list of time ordered signals searching for a signal which occurred at a specified time and returns the signal. The send_signal() method of the Wire class adds a specified signal to its linked list of signals and then attempts to propagate it to all components in its fan-out component list using the propagate() method it inherited from its base Connector class.
The Port class is derived from the Connector class. This class overrides both the get_signal() and send_signal() methods of its base class. In addition to the derivation relationship between the Port class and the Connector class, an aggregation relationship also exists between these two classes. Unlike previous containment associations, the filled rectangle has been replaced with a hollow rectangle. This adornment suggests containment by pointer (or reference) -- the Port class contains a pointer to a Connector class. This pointer is referred to as the Port's external connector. Because a pointer to a base class can legitimately point to any of its derived classes, this external Connector pointer can point to either another Port class or to a Wire class.
This pointer containment relationship is required for the hierarchical transmission of signals throughout the circuit. Both the get_signal() and send_signal() methods of the Port class exploit this relationship so as to vertically traverse the hierarchical representation during simulation. The get_signal() method of the Port class travels recursively up the hierarchy via the external connectors until it encounters a wire, at which point the get_signal() method of the Wire class returns the requested signal from its signal list. Similarly, the send_signal() method of the Port class transmits a signal out via a series of external connectors until a wire is encountered, at which point the send_signal() method of the Wire object stores the signal and propagates it. The send_signal() method of the Port class then propagates the signal to all components in its fan-out. The two send_signal() methods of the Port and Wire classes are presented in Figure 4.8 and Figure 4.9, respectively.
Figure 4.8: Sending a signal to a Port
Figure 4.9: Sending a signal to a Wire
Note that the abstract class adornment is still present on the Port class. This is because the Port class is still too generic to be used for object instantiation. We prevent instantiation of the Port class by making its constructor protected, thereby implying that only derived classes may call its constructor. These derived classes can subsequently be used for instantiation.
Finally, the Input and Output classes are derived from the Port class. Because the constructors for these two classes are public, components may include input and output port objects as part of their representation. The only major point of note here is that the Input class redefines its send_signal() message to display an error instead of sending a signal, because input ports can only receive signals, not send them.
By using various combinations of classes derived from the Component class and Connector class, it is possible to build arbitrary blocks of logic. For example, consider the construction of a 3-input AND gate using two 2-input AND gates. Such a gate would contain three input ports, one output port, two instances of 2-input AND gates and a wire. The constructor would then be responsible for connecting all the ports and wires together in some meaningful fashion. A Booch diagram representing a 3-input AND gate is presented in Figure 4.10.
Figure 4.10: Class diagram of a 3-input AND gate
The only issue left to address is how the classes described above interact to simulate a circuit. The fundamentals behind the simulation algorithm are discussed in the next section.