Akita Basics 1: Events

Yifan Sun
Akita Simulation
Published in
4 min readDec 2, 2019

The goal of Akita is to create a reusable computer architecture framework. With Akita, developers can fast prototype their hardware design.

We started the Akita project since February 2017. In the beginning, we did not consider building a framework, but a new GPU simulator. As our development continues, we gradually realized the importance of an easy-to-use framework. Our core design can apply to the development of other simulators. Therefore, we isolated the framework design and called it Akita.

The code name Akita is a Japanese city. Akita is also a dog breed, famous for its loyalty. The Akita logo embeds the letters of A-K-I-T-A in a dog face.

Since this is the first blog in the series, I need to explain one of the most crucial decisions: Why we select Go? The computer architecture research community has a long history of using C++ as the simulator developing language. I decided not to use C++ due to my lack of skill in C++. C++ is too complicated for me. Before we start using Go, we considered and experimented with Java and JavaScript. We do not consider python due to performance issues. We eventually ditched Java because Java’s package management is cumbersome. Wait, did I mentioned JavaScript?

Let’s come back to events.

In Akita, an event is an action to happen in the future. Events do not have durations. So the action of an event happens instantaneously. An event has two mandatory properties, the time and the handler. Both fields are immutable. Those fields can only be assigned when event initiates and cannot be updated afterward.

The core of a simulator is an event-driven simulation engine, or engine for short. The engine maintains a queue of events and triggers events in the chronological order. The engine only has two core interface functions, Schedule and Run. Schedule registers an event to be triggered in the future. Run triggers all the events until there is no event left in the event queue.

In most computer architecture simulators, the cycle (as integer) is a first-order concept. The time can be inferred from the current cycle. In Akita, all events directly use time as a double-precision floating-point number. All times are represented in second to avoid confusion. Using floating-point time simplifies simulations that incorporate components that run at different frequencies. It is also easier to model time-varying period duration, like in dynamic voltage frequency scaling (DVFS).

Each event has a handler. Different handlers can handle events of the same type. So, events of the same type may demonstrate very different behaviors. One handler may also handle different types of events.

Let’s show how to use the event system with a simple example. Suppose we have one small bacteria at the beginning. It splits into two at the second 1. After that, every bacteria waits for a random time between 0.5–2.5 seconds before the next split. We want to run a simulation that can count the number of bacteria at the 10th second.

The first step is to declare an event. We will mention the function IsSecondary in a later blog.

type SplitEvent struct {
time akita.VTimeInSec
handler akita.Handler
}
func (e SplitEvent) Time() akita.VTimeInSec {
return e.time
}
func (e SplitEvent) Handler() akita.Handler {
return e.handler
}
func (e SplitEvent) IsSecondary() bool {
return false
}

Then, we need to define the action associated with a split event. We can define the action in an event handler.

type SplitHandler struct {
total int
engine akita.Engine
}
func (h *SplitHandler) Handle(evt akita.Event) error {
h.total++
now := evt.Time()
nextTime := now + akita.VTimeInSec(rand.Float64()*2+0.5)
if nextTime < 10.0 {
nextEvt := SplitEvent{
time: nextTime,
handler: h,
}
h.engine.Schedule(nextEvt)
}
nextTime = now + akita.VTimeInSec(rand.Float64()*2+0.5)
if nextTime < 10.0 {
nextEvt := SplitEvent{
time: nextTime,
handler: h,
}
h.engine.Schedule(nextEvt)
}
return nil
}

Here, when a handler handles one event, it can schedule future events. In our example, when one event is handled, one bacteria becomes two. And we schedule the 2 future split events if the events will happen before the 10th second.

Finally, we put the code together in the main program.

func main() {
engine := akita.NewSerialEngine()
splitHandler := SplitHandler{
total: 0,
engine: engine,
}
engine.Schedule(SplitEvent{
time: 0,
handler: &splitHandler,
})
engine.Run() fmt.Printf(“Total number at time 10: %d\n”, splitHandler.total)
}

In the main function, we first create a serial engine and a splitHandler. We then schedule the first split event to create the first bacteria and to kick start the whole simulation. The simulation runs with engine.Run(). Finally, in the last line of the function, we print the total number of bacteria. In my test, I can get a total number of 185 bacteria at the 10th second.

The code is available at https://play.golang.org/p/jjVIDdV2v7H.

In summary, Akita is a pure event-driven simulation framework. The event is the most important concept in Akita. Akita defines event with double precision floating point time. Developers can define actions with event handlers.

Next: components, ports and connections.

--

--

Yifan Sun
Akita Simulation

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