Akita Basics 2: Components, Messages, Ports, and Connections

Yifan Sun
Akita Simulation
Published in
3 min readDec 27, 2019

In the last blog, we introduced events and event engines in Akita. In this blog, we introduce another group of concepts: components, ports, and connections.

Components are entities under simulation. Simulator developers can decide what a component is. In MGPUSim, a cache module, a DRAM controller, a TLB are all examples of the component.

All components are event handlers and implement the handle function. One component can handle multiple types of events.

A component should be fully encapsulated. A component should not directly access fields of other components and call functions of other components. Moreover, a component cannot schedule an event for another component. Each component should be a self-sufficient entity that has all the states to perform its tasks.

Here is an example that may be commonly used in other simulators. Assuming we have a CPU core and an L1 cache. When the CPU core needs to access memory, it sends a read request to the L1 cache. However, if the L1 cache is busy, the core has to stall. A simple solution is to let the CPU call a function like l1.isBusy() before sending the request.

This solution works well as designed. But problems arise as the requirements get complex. 1. What if a user wants to model a system without caches. Does a memory controller also have an isBusy() function? 2. What if the simulation runs in parallel. The L1 cache may update its busy status while the core access the field? How can we lock variables properly?

Akita solves the problems above by using a Message-Port-Connection system. Developers should explicitly define the protocols between components so that they can communicate with certain types of messages. Components that follow the same protocol (e.g., L1 cache and DRAM controller) can replace each other. Each component can decide how exactly to handle each request. Since Akita forbids cross-component field access, concurrent variable access is more predictable. Locks only need to be applied at the component level.

A message is a basic element that two components communicate. It has a few basic properties (metadata), such as send time, receive time, source, and destination. The Message interface defines only one Meta() *MsgMeta function. The Meta function returns a pointer to the MsgMeta struct that holds the basic properties of the message. A message can also carry message-specific data. For example, a memory read request message can carry the address to read and the number of bytes to read in the message.

A component has one or more ports. Messages go through ports. When a component needs to send a message out, it needs to send through the port. The key function of a port is the Send(msg Msg) *SendError function. It receives a message as an argument and returns a SendError if the send fails. If the Send function returns nil, the port guarantees delivery. Incoming messages also go through ports. When a message arrives at a port, the connection calls the ports Recv(msg Msg) *SendError function. If the recv function returns an error, the port can currently not receive the message, and the connection retries the delivery at a later time. Otherwise, the port buffers the message. The component can call Peek() Msg and Retrieve(now akita.VTimeInSec) Msg function to access the messages arrived at the port.

Connections provide the message channel from a port to another. A connection and plug in multiple ports, but a port can only link with one connection. The responsibility of a connection is to deliver the messages sent between the ports.

The component-port-connection system is sufficient to create many complex simulations. In the next blog, we are going to give an example of how to use the components, ports, and connections to write a simple simulation.

Next: Ping Simulation Example.

--

--

Yifan Sun
Akita Simulation

Assistant Professor @ William & Mary, Computer Architect, Computer Architecture Simulator Designer, Go Programmer