Every signal is composed of two elements: a signal value (for example, HIGH, LOW, X) and a time when that value was produced. They are stored in linked lists in much the same way that the Component_List class stores components.
The Signal class is declared as follows:
class Signal { friend ostream &operator <<(ostream &, const Signal &); public: Signal(Sig_Val = X, ckt_time = INIT_TIME); operator Sig_Val(); ckt_time get_time(); private: Sig_Val value; ckt_time t; };
Note that Sig_Val is implemented as an enumerated type. The signal constructor trivially assigns a signal value and a time to the hidden members of the class.
Signal::Signal(Sig_Val sv, ckt_time ct) : value(sv), t(ct) { }
The operator Sig_Val() function is a special function called a user-defined conversion. It returns the value field whenever a signal object is used in the context where a Sig_Val is expected. This function essentially converts a signal into a signal value, and its usefulness will become apparent when the process() method for a component is described later in this chapter.
Signal::operator Sig_Val() { return value; }
The get_time() method is simply an access method which returns the time that the signal occurred.
ckt_time Signal::get_time() { return t; }
The ostream &operator <<(ostream &, const Signal &) function enables the signal's value and time to be output using the << operator. This function is made a friend of the class so it has access to the hidden members of the class. Overloading this operator enables signals to be treated just like other built-in types which are output using the same technique.
The Signal_List class is declared as follows:
class Signal_List { public: Signal_List(); void add(Signal); Signal find(ckt_time); void dump(); private: Signal_Node *sig_list; };
The constructor and the add() method are identical to the corresponding methods in the Component_List class. The add() function also checks to make sure that signals enter the list in the correct time order. Should a signal be found whose time is less than or equal to the last signal on the wire, a warning message is displayed.
The find() method simply scans the nodes of the signal list searching for the signal which occurred at the specified time. If a signal could not be found, an undefined signal is returned.
Signal Signal_List::find(ckt_time t) { // Make sure we are not looking too far in the future. if (sig_list->sig.get_time() < t) return Signal(UNDEF_SIG, UNDEF_TIME);
for (Signal_Node *list = sig_list; list != 0; list = list->next) { if (list->sig.get_time() == t) return list->sig; if (list->next != 0 && list->next->sig.get_time() <= t) return list->next->sig; } // If signal not found, return an error signal. return Signal(UNDEF_SIG, UNDEF_TIME); }
The display() method simply traverses the signal list, displaying each signal it encounters using the overloaded << operator.
void Signal_List::dump() { for (Signal_Node *list = sig_list; list != 0; list = list->next) { cout << list->sig; } }
Signal transmission occurs using the get_Signal() and send_Signal() methods which are defined as virtual methods in the Wire and Port class. These two functions were defined virtually because the method that a Port uses to get and send a signal is quite different from the method used by a Wire. A wire gets a signal by sending a find() message to its encapsulated signal list. A Port, however, gets a signal by sending a get_Signal() message to the connector which feeds it. Hence, the get_Signal() message for a wire and a port is as follows:
Signal Wire::get_Signal(ckt_time t) { return signals.find(t); } Signal Port::get_Signal(ckt_time t) { return external->get_Signal(t); }
Note that the last call appears to be recursive with no terminating condition. However, it must be remembered that get_Signal() is a virtual function, and therefore is bound at runtime depending upon whether external is a pointer to a wire or a pointer to another port. As such, the call to get_Signal() in Port::get_Signal() will recurse only if external points to another port. Otherwise, it will call Wire::get_Signal() meaning that external points to a wire. Since every port must point directly or indirectly to a wire, it is guaranteed that this ``recursion'' will terminate.
The send_Signal() command operates in a similar fashion except that in both the wire and port class, the signal is propagated to all the components in the fan-out of the wire/port.
void Wire::send_Signal(Signal s) { signals.add(s); fan_out.propagate(); } void Port::send_Signal(Signal s) { external->send_Signal(s); fan_out.propagate(); }
The details of the propagate() function are presented in the next next section along with the entire simulation algorithm.