A Fast and Furious Introduction to Sparta
07 Nov 2021 #modelling #architecture #micro-architectureSparta is a modeling framework that can be used for a variety of architecture (or otherwise) modelling work. In this blog post, I present an introduction to the basic bells and whistles of the framework. The entire tutorial is available via Github.
Usecase
We extend the basic example to introduce a new sparta::Unit
Pipe.
Producer sends data to a Pipe. Pipe receives data and forwards this to a consumer. Consumer signals the Pipe that
data is received. In turn, Pipe signals the producer that data has been received and the cycle continues.
Pipe definition (Pipe.hpp)
Each sparta unit should have a ParameterSet (subclass of sparta::ParameterSet
). With each parameter, there can be a validation method attached,
to check if valid values were set to the parameter and a name of the unit as well.
For the Pipe, we consider 4 ports — two data ports and two signal ports.
sparta::DataOutPort<uint32_t> pipe_out_port_{&unit_port_set_, "pipe_out_port"};
sparta::DataInPort<uint32_t> pipe_in_port_ {&unit_port_set_, "pipe_in_port", sparta::SchedulingPhase::PortUpdate, 1};
sparta::SignalInPort pipe_go_port_ {&unit_port_set_, "pipe_go_port"};
sparta::SignalOutPort pipe_go_producer_port_ {&unit_port_set_, "pipe_go_producer_port"};
The DataInPort receives the data from the producer and sends it to the consumer using the DataOutPort. Similarly, it receives acknowledgement of the receipt by the consumer using the SignalInPort, which it passes along to the producer using the SignalOutPort.
Corresponding to each input port, we can define a handler to perform some action on the received data.
// Pipe receive handler
// receive the data from the producer
void receiveData_(const uint32_t & dat);
// receive signal from the consumer and send a singal to the producer
void receiveSignal_();
We can also define events, that will be triggered after a specified duration on scheduling. For example,
we define a event to delay signalling the producer, after a signal is received from the consumer. In this case,
delay is 5 cycles and sendData_
is called as its handler.
// An event to be scheduled in the sparta::SchedulingPhase::Tick
// phase if data is received
sparta::Event<> event_do_some_work_{&unit_event_set_, "do_work_event",
CREATE_SPARTA_HANDLER(Pipe, sendData_),5};
Statistics can be measured quite easily.
// Stats
sparta::Counter num_processed_{&unit_stat_set_, "num_processed_",
"Number of items produced", sparta::Counter::COUNT_NORMAL};
To use such a counter, a simple ++num_processed_
would suffice.
Finally, loggers can be set up per Unit.
sparta::log::MessageSource pipe_info_;
Pipe implementation (Pipe.cpp)
- Start by defining the name of the Unit
- The handler methods are tied to the ports in the constructor.
// Register a handler when the producer sends a data pipe_in_port_.registerConsumerHandler(CREATE_SPARTA_HANDLER_WITH_DATA(Pipe, receiveData_, uint32_t)); // Register a go-handler when the consumer sends a go request pipe_go_port_.registerConsumerHandler(CREATE_SPARTA_HANDLER(Pipe, receiveSignal_));
These methods are called when the data is received at the port.
- We schedule the event inside
receiveData_
.event_do_some_work_.schedule();
This would triggersendData_
after 5 cycles.
Building the simulator tree (StageSim{.hpp,.cpp})
StageSim.hpp
is the top level module definition.- In
StageSim.cpp
, a bunch of setup needs to be done, in order to build the simulator tree.- In the constructor, the units and the corresponding parameter sets are made available.
getResourceSet()->addResourceFactory<sparta::ResourceFactory<Pipe, Pipe::PipeParameterSet>>();
- The destructor is relatively simple.
buildTree_
is one of the important things. Basically, each node is created as aResourceTreeNode
and added to theto_delete_
vector. Reading the parameters is performed and the constructor is called using these parameters.configureTree_
is not used in the current context.bindTree_
is used to connect the different ports! An example is// bind pipe to the producer sparta::bind(root_tree_node->getChildAs<sparta::Port>(nodeName.str() + ".ports.producer_out_port"), root_tree_node->getChildAs<sparta::Port>("pipe.ports.pipe_in_port"));
It can be observed that the port names are hierarchical.pipe.ports.pipe_in_port
:pipe
is the unit, under whichports
is the set of ports andpipe_in_port
is the name of the port.
- In the constructor, the units and the corresponding parameter sets are made available.
Command line simulator setup (main_3s.cpp)
- Not much changes were made to the original example, except the following.
// Create the simulator object for population -- does not // instantiate nor run it. sparta::Scheduler scheduler; StageSim sim(scheduler, be_noisy);
- Now, we are ready to run the simulation
cls.populateSimulation(&sim); cls.runSimulator(&sim); cls.postProcess(&sim);
Running the example
In the current environment, the following modules were available. Currently Loaded Modulefiles:
1) GCCcore/10.3.0 5) numactl/2.0.14-GCCcore-10.3.0 9) hwloc/2.4.1-GCCcore-10.3.0 13) libfabric/1.12.1-GCCcore-10.3.0 17) FlexiBLAS/3.0.4-GCC-10.3.0 21) foss/2021a 25) Tcl/8.6.11-GCCcore-10.3.0 29) RapidJSON/1.1.0-GCCcore-10.3.0 33) cmake-3.17.5
2) zlib/1.2.11-GCCcore-10.3.0 6) XZ/5.2.5-GCCcore-10.3.0 10) OpenSSL/1.1 14) PMIx/3.2.3-GCCcore-10.3.0 18) gompi/2021a 22) yaml-cpp-0.6.2 26) SQLite/3.35.4-GCCcore-10.3.0 30) bzip2/1.0.8-GCCcore-10.3.0
3) binutils/2.36.1-GCCcore-10.3.0 7) libxml2/2.9.10-GCCcore-10.3.0 11) libevent/2.1.12-GCCcore-10.3.0 15) OpenMPI/4.1.1-GCC-10.3.0 19) FFTW/3.3.9-gompi-2021a 23) ncurses/6.2-GCCcore-10.3.0 27) Szip/2.1.1-GCCcore-10.3.0 31) GMP/6.2.1-GCCcore-10.3.0
4) GCC/10.3.0 8) libpciaccess/0.16-GCCcore-10.3.0 12) UCX/1.10.0-GCCcore-10.3.0 16) OpenBLAS/0.3.15-GCC-10.3.0 20) ScaLAPACK/2.1.0-gompi-2021a-fb 24) libreadline/8.1-GCCcore-10.3.0 28) HDF5/1.10.7-gompi-2021a 32) boost-1.77.0 ```
To build the simulator,
mkdir build
cd build
cmake ..
make sparta_3stage
Sparta has a ton of options, which can be viewed as follows:
sparta_3stage --help
-
To see the simulation tree,
sparta_3stage --show-tree
This is a relatively simple way to see which ports are connected to what.
top : <top (root)> (privacy: 0) +-descendant_attached : <top.descendant_attached name:"descendant_attached" datat:(sparta::TreeNode) observers:1 posted:55> (privacy: 0) +-consumer : <top.consumer resource: "consumer"> (privacy: 0) | +-params : <top.consumer.params 1 params> {builtin} (privacy: 0) | | +-num_producers : [<top.consumer.params.num_producers tags:[SPARTA_Parameter]>]<param uint32_t num_producers=1, def=1, write=0 read: 1 ignored: 0> (privacy: 0) | +-ports : <top.consumer.ports> (privacy: 0) | | +-consumer_in_port : [bound to] {pipe_out_port (top.pipe.ports.pipe_out_port)} (privacy: 0) | | | +-events : <top.consumer.ports.consumer_in_port.events 1 events> {builtin} (privacy: 0) | | | | +-consumer_in_port_forward_event : <top.consumer.ports.consumer_in_port.events.consumer_in_port_forward_event> (privacy: 0) | | +-producer0_go_port : [bound to] {pipe_go_port (top.pipe.ports.pipe_go_port)} (privacy: 0) | +-events : <top.consumer.events 1 events> {builtin} (privacy: 0) | | +-ev_data_arrived : <top.consumer.events.ev_data_arrived> (privacy: 0) | +-stats : <top.consumer.stats 0 stats, 1 counters> {builtin} (privacy: 0) | | +-num_consumed : <top.consumer.stats.num_consumed val:100 normal vis:100000000> (privacy: 0) | +-? : <top.consumer:log_msg_src cat:"info" observed:false msgs:0> (_sparta_log_msg_source_[0]) (privacy: 0) | +-? : <top.consumer:log_msg_src cat:"warning" observed:true msgs:0> (_sparta_log_msg_source_[1]) (privacy: 0) | +-? : <top.consumer:log_msg_src cat:"debug" observed:false msgs:0> (_sparta_log_msg_source_[2]) (privacy: 0) | +-? : <top.consumer:log_msg_src cat:"info" observed:false msgs:0> (_sparta_log_msg_source_[3]) (privacy: 0) +-pipe : <top.pipe resource: "pipe"> (privacy: 0) | +-params : <top.pipe.params 0 params> {builtin} (privacy: 0) | +-ports : <top.pipe.ports> (privacy: 0) | | +-pipe_out_port : [bound to] {consumer_in_port (top.consumer.ports.consumer_in_port)} (privacy: 0) | | +-pipe_in_port : [bound to] {producer_out_port (top.producer0.ports.producer_out_port)} (privacy: 0) | | | +-events : <top.pipe.ports.pipe_in_port.events 1 events> {builtin} (privacy: 0) | | | | +-pipe_in_port_forward_event : <top.pipe.ports.pipe_in_port.events.pipe_in_port_forward_event> (privacy: 0) | | +-pipe_go_port : [bound to] {producer0_go_port (top.consumer.ports.producer0_go_port)} (privacy: 0) | | | +-events : <top.pipe.ports.pipe_go_port.events 1 events> {builtin} (privacy: 0) | | | | +-pipe_go_port_forward_event : <top.pipe.ports.pipe_go_port.events.pipe_go_port_forward_event> (privacy: 0) | | +-pipe_go_producer_port : [bound to] {producer_go_port (top.producer0.ports.producer_go_port)} (privacy: 0) | +-events : <top.pipe.events 1 events> {builtin} (privacy: 0) | | +-do_work_event : <top.pipe.events.do_work_event> (privacy: 0) | +-stats : <top.pipe.stats 0 stats, 1 counters> {builtin} (privacy: 0) | | +-num_processed_ : <top.pipe.stats.num_processed_ val:101 normal vis:100000000> (privacy: 0) | +-? : <top.pipe:log_msg_src cat:"info" observed:false msgs:0> (_sparta_log_msg_source_[0]) (privacy: 0) | +-? : <top.pipe:log_msg_src cat:"warning" observed:true msgs:0> (_sparta_log_msg_source_[1]) (privacy: 0) | +-? : <top.pipe:log_msg_src cat:"debug" observed:false msgs:0> (_sparta_log_msg_source_[2]) (privacy: 0) | +-? : <top.pipe:log_msg_src cat:"info" observed:false msgs:0> (_sparta_log_msg_source_[3]) (privacy: 0) +-producer0 : <top.producer0 resource: "producer"> (producer[0]) (privacy: 0) | +-params : <top.producer0.params 3 params> {builtin} (privacy: 0) | | +-max_ints_to_send : [<top.producer0.params.max_ints_to_send tags:[SPARTA_Parameter]>]<param uint32_t max_ints_to_send=100, def=100, write=0 read: 1 ignored: 0> (privacy: 0) | | +-test_param : [<top.producer0.params.test_param tags:[SPARTA_Parameter]>]<param uint32_t test_param=1, def=0, write=1 read: 1 ignored: 0 VOLATILE> (privacy: 0) | | +-arch_override_test_param : [<top.producer0.params.arch_override_test_param tags:[SPARTA_Parameter]>]<param std::string arch_override_test_param=reset_in_constructor, def=arch_override_default_value, write=1 read: 0 ignored: 1> (privacy: 0) | +-ports : <top.producer0.ports> (privacy: 0) | | +-producer_out_port : [bound to] {pipe_in_port (top.pipe.ports.pipe_in_port)} (privacy: 0) | | +-producer_go_port : [bound to] {pipe_go_producer_port (top.pipe.ports.pipe_go_producer_port)} (privacy: 0) | | | +-events : <top.producer0.ports.producer_go_port.events 1 events> {builtin} (privacy: 0) | | | | +-producer_go_port_forward_event : <top.producer0.ports.producer_go_port.events.producer_go_port_forward_event> (privacy: 0) | +-events : <top.producer0.events 1 events> {builtin} (privacy: 0) | | +-ev_producing_event : <top.producer0.events.ev_producing_event> (privacy: 0) | +-stats : <top.producer0.stats 0 stats, 1 counters> {builtin} (privacy: 0) | | +-num_produced : <top.producer0.stats.num_produced val:100 normal vis:100000000> (privacy: 0) | +-? : <top.producer0:log_msg_src cat:"info" observed:false msgs:0> (_sparta_log_msg_source_[0]) (privacy: 0) | +-? : <top.producer0:log_msg_src cat:"warning" observed:true msgs:1> (_sparta_log_msg_source_[1]) (privacy: 0) | +-? : <top.producer0:log_msg_src cat:"debug" observed:false msgs:0> (_sparta_log_msg_source_[2]) (privacy: 0) | +-? : <top.producer0:log_msg_src cat:"info" observed:false msgs:0> (_sparta_log_msg_source_[3]) (privacy: 0) +-sparta_expression_trigger_fired : <top.sparta_expression_trigger_fired name:"sparta_expression_trigger_fired" datat:(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >) observers:0 posted:0> (privacy: 0)
- To generate an html report,
./sparta_3stage --report-all output.html html
. The report format could also beyaml
. -
Selective logging can be done. For example to view logs related to
pipe
,./sparta_3stage -l top.pipe info pipe_info.txt
This would dump the information in a log file,pipe_info.txt
. - A list of command line usage scenarios is already documented in the main (documentation](https://sparcians.github.io/map/core_example.html) of Sparta.
References
- Sparta Docs : [Link]
- Tutorial Code: [Github link]