A SID component is a C++ object of type sid::component which calls, and is called by, other SID components. A complete simulation is a set of components connected together. Simulations are single-threaded, and there is no underlying "supervisory" simulation code. As the simulation runs, control repeatedly loops within a "root" component, calling into its connected components, which perform any local simulation tasks and in turn call their connected components before returning control to their caller. This cycle continues until the simulation is terminated.
SID Components come equipped with a set of string-valued attributes, which can be queried and set during configuration or simulation, to inspect or modify the behavior of the component. Attributes generally provide information and control which is of interest to the simulation user, rather than to other components in the simulation. Conventionally, attributes are grouped into named "categories" by role, though attributes may have no category, and may in addition be accessed without regard to their category. Categories merely assist in treating a group of attributes similarly, for example in a simulator's graphical user interface.
SID models a component's communication connections as a set of buses and pins. These terms are intended to reflect the design of the hardware being simulated, and SID's API presents these terms as the abstract C++ classes sid::bus and sid::pin. A SID component may have many pins and many buses, each of which represents the receiving end of a connection to another component.
A pin represents a non-refutable, one-way connection through which an integer value may be "driven". Pins are best thought of as "triggers", which may carry an informative value but which do not have any sort of request/response mechanism built into them. A bus, on the other hand, represents a read/write interface through which integer values and addresses are transmitted. Every bus request returns a sid::bus::status object, which describes both the success or failure of the request and the latency of the bus transaction, as modeled by the recipient. Buses are best thought of as small windows into the recipient's memory or register set, through which the sender may attempt to read or write values.
Pins and buses do not in general exist beyond the scope of the component they belong do; a component is usually responsible for managing the lifecycle of its own pins and buses. Moreover components do not often set up their own connections. Instead, each component presents a set of "factory methods", which can be called with string names of buses or pins. The component must then choose to either construct a pin or bus "on demand", serve a pin and bus from a fixed set compiled into the component, or respond with a NULL pointer, indicating an unknown pin or bus was requested. A component may also be asked to place a pointer to a given pin or bus in a named slot within itself, which it will use as the sending end of a connection during simulation. Slots for buses are referred to as "accessors", whereas slots for pins are simply "output pins".
Example 1. Bus Connection
It is very important to understand the relationship between sending and receiving ends of bus and pin connections, so we present an example here. Suppose we load a CPU component C and a memory component M. Suppose that C has an accessor named "system-mem" which it intends to use for reading and writing to some sort of memory device, and suppose that M has a bus named "data-bus" through which it expects to receive read/write requests. The scenario is depicted blow.
Recall that the accessor is the sending end of the connection we wish to make, and that the bus is the receiving end. Some people find the terms "master" and "slave" clearer than "sender" and "receiver", since information is permitted to flow both ways on a bus. In the master/slave terminology, the bus master makes bus requests; the bus slave services them. Whether the bus requests are reads or writes is irrelevant to this relationship. The following code will establish the connection:
sid::bus *memory_data_bus = M.find_bus ("data-bus"); C.connect_accessor ("system-mem", memory_data_bus);
Most connections are not established by hand this way, but are done by a "configuration root" component, which reads a simple configuration command language, loads and instantiates components, hooks up buses to accessors, output pins to input pins, and sets attribute values. Each of these configuration commands is closely mirrored by a small sequence of SID API calls, such as in the above example.
Note that in this example only one component is transmitting signals to the memory's bus. This is not intrinsic to the connection mechanism; connections are usually just assignments to pointer variables within the sending component. There could just as easily be several components all sending to the same bus at various times during simulation. Additionally, while an accessor can only ever be connected to one bus, an output pin can be connected to multiple input pins; driving the output simply drives all the connected pins. Obviously this arrangement does not make sense for buses, in which each bus request results its own unique status code. To connect one accessor to multiple buses, one needs a bus mapper to arbitrate the connection. See the component reference manual for details on bus mappers.
In addition to pins, buses and attributes, SID components may have named slots for holding pointers to other components. Such an association, when made in terms of the sid::component interface, is called a "relationship" of the pointer-holder. A component may hold many pointers to different components in a single relationship, and may have components placed in its relationship lists by the configuration process.
Relationships give components the ability to call related components' sid::component API, rather than simply communicate with them through sid::bus and sid::pin connections. If such a capability is not strictly required, the more conservative bus and pin communication APIs are generally preferred for the sake of minimizing potential bugs.
Components are conventionally packaged into separate shared object (.so) or dynamic link library (.DLL) files, and loaded on demand into the SID process by the configuration component. Each such file may contain several component classes, but by convention such components are functionally similar. SID specifies an API for querying such files and instantiating their components. Any file which implements a component must support this API for SID to make use of it.