The Sparta Modeling Framework
|
In addition to providing a simulation command line infrastructure (see Simulator Configuration) as well as resource creation (see resources) in a tree form for organization (see trees), Sparta provides a series of classes and ordering specifically dedicated for resource to resource communication as well as internal resource communication. This communication can be timed or untimed as desired.
The first point of Sparta's communication philosophy lies behind the timing aspects of Sparta: There are scheduling phases (sparta::SchedulingPhase) that allows a user to count on certain ordering between resources and another. This is different from prior versions of Sparta where every scheduleable type of event was in a single phase and required the user to provide explicit orderings.
Ordering within a phase, however, is still supported, allowing a modeler to specify the order in which events are scheduled/fired within that phase. For example, a modeler may desired eventA which reads from a Port to be scheduled before eventB which might act on the data read.
The classes that support communication and scheduling are:
The below sections detail each. To build the examples documented in this section, please see the README.md for directions/prerequisite for creating a build directory. Then, build in the example/Documentation/communication directory:
Ports are the mechanism in which a resource communicates a message to another resource (for example, via the method sparta::DataOutPort::send). The two resources do not know about each other and don't need to. Typically, during sparta::app::Simulation::finalizeTree (calls sparta::app::Simulation::bindTree_) phase, any sparta::Port derived classes that are constructed with a sparta::PortSet are bound together using sparta::bind(sparta::Port, sparta::Port) methods. An example of how this is done is found in details section of sparta::DataOutPort as well as the example core simulator's ExampleSimulator::buildTree_ (see Core Example Using Sparta) method (source found in example/CoreExample/src).
For the receiver of the data, the receiver would have to register a callback on the sparta::DataInPort via the sparta::DataInPort::registerConsumerHandler. This callback must be a member of the containing class or another persistent class in simulation.
Example of a device receiving data can be found in example/Documentation/communication/Ports_example.hpp
:
To enable communication between components on different Clock boundaries, use sparta::SyncInPort and sparta::SyncOutPort. This type of port is identical to DataIn/OutPort with the exception that data sent on the out port is delayed until the "rising edge" of the receiver's clock. See sparta::SyncOutPort for more information.
Events are mechanism to allow the scheduling of work based on activity within a resource. For example, the receiving of data from an external resource on a port might trigger the need to act upon that data and send it on. Take the previous example in the Port section and add an event based on the receiving of data (found example/Documentation/communication/Events_example.hpp).
This isn't a very interesting class as myDataReceiver_()
could easily just call doSomeWork_()
directly. But, what if MyDevice
had two inports that needed to be called before doing some work? That's no problem either. Let's extend the class, but this time adding a second port and changing event_do_some_work_ from a sparta::Event to a sparta::UniqueEvent to ensure it gets called only once when scheduled by both handlers. Code found in example/Documentation/communication/Events_dual_example.hpp.
Sparta's clocking and clock management are contained in sparta::Clock and sparta::ClockManager classes. The main difference to note in Sparta, is that sparta::Clock objects are not event-based objects nor do they constantly "clock" as simulation progresses (like in other frameworks). sparta::Clock objects are simply available to convert simulation time (from the sparta::Scheduler) to clock time based on that clock's frequency. Therefor, in simulation, sparta::Clock objects are passed around as constant objects. In addition, there is no notion of a "rising edge" nor a "falling edge" in simulation (however, the sparta::Clock supports this notion if absolutely needed). Instead, the Clocks are used to simply answer the question, what time (in NS) on the Scheduler should Event X be scheduled?
Using the sparta::ClockManager and the sparta::Clock classes are pretty straight-forward. By default the sparta::ClockManager contains a root clock that runs at the Scheduler frequency: 1 cycle == 1 NS. This is called the master clock (and denotes the "hypercycle"). From the master clock, more clocks can be created and then associated with sparta::TreeNode objects that Device objects hang off of:
The sparta::Scheduler class simply schedules callback methods for some time in the future. These callbacks are typically scheduled by sparta::Scheduleable class and its derivatives, but the sparta::scheduler is open to anyone who wishes to schedule a callback (but this is highly discouraged).
The callback type is sparta::SpartaHandler, a copyable method delegate that allows a user to specify a function of their class as a callback point. The callback form is expected to be of the following:
Time in the sparta::Scheduler can be interpreted anyway the user wishes, but the base unit is a sparta::Scheduler::Tick. For most simulation uses, the Tick is considered a PS of time. To convert a Tick to a higher-order unit such as a clock cycle, use a sparta::Clock made from a sparta::ClockManager to perform the conversions.
A typical flow for scheduling events is:
The sparta::Scheduler is a sparta::RootTreeNode so that it can be seen in a global search scope, and loggers can be attached. For example, try this on the CoreExample:
Or do this in your C++ code:
The sparta::Scheduler has a concept of "phased grouping" that allows a user to specify which callback they want called before another in time. Each SPARTA event type has an associated sparta::SchedulingPhase phase in its template parameter list that the event will always be placed in. In that phase, the event will always come before a "higher priority phase" and always after a "lower priority phase." But, within its assigned phase, the event will still be semi-random with respect to other events. It's "semi-random," meaning order will be indentical between simulation runs, but possibly different once the simulator is modified at the source-code level. This can be annoying.
Ordering within a phase is provided by a Direct Acyclic Graph or sparta::DAG. The DAG uses a class called sparta::Scheduleable that represents a position within the DAG and an ordering group within a SchedulingPhase. By default the sparta::Scheduleable is not in a group and is standalone within its assigned sparta::SchedulingPhase. Once a precedence between two sparta::Scheduleable objects is established, an ordering with assigned. This results in each sparta::Scheduleable being designated into an ordering group by the DAG. Event types (sparta::Event, sparta::UniqueEvent, sparta::PayloadEvent) provide this support. The developer can order an event type to precede another event, but only if the events are in the same SchedulingPhase:
The sparta::Scheduler is responsible for finalizing the DAG. The DAG is finalized when the sparta::Scheduler is finalized through the sparta::Scheduler::finalize method called by the framework. Therefor, all events can only be scheduled after the sparta::scheduler is finalized. It is illegal to schedule events before the dag is finalized because precedence has not been fully established. Any startup work can be scheduled via the sparta::StartupEvent class before sparta::Scheduler finalization.
The expected usage is something like:
This class will allow a Sparta developer to interoperate a Sparta-based simulator with the SystemC kernel. The general rule of thumb is that the Sparta scheduler is either always equal to or 1 cycle ahead of the SystemC scheduler. The Sparta Scheduler will "sleep" waiting for SysC to catch up the next scheduled Sparta event.
There are two ways to stop simulation using this adapter:
There are some caveats to know about this adapter. See the todo.