SoOSiM

This package is not currently in any snapshots. If you're interested in using it, we recommend adding it to Stackage Nightly. Doing so will make builds more reliable, and allow stackage.org to host generated Haddocks.

Component definition Step-by-Step

We start by defining the name of our Haskell module, in this case MemoryManager.
Make sure the name of file matches the name of the module, where haskell src files use the .hs file-name extension.

We continue with importing modules that we require to build our component:

import SoOSiM
import MemoryManager.Types
import MemoryManager.Util

The SoOSiM module defines all the simulator API functions.
Besides the external module, we also import two local module called MemoryManager.Types and MemoryManager.Util, which we define in ./MemoryManager/Types.hs and ./MemoryManager/Util.hs respectively.

We start our description with a datatype definition describing the internal state of our memory manager component, and the datatype encoding the messages our memory manager will receive:

We define two record datatypes [1]; and with three fields (baseAddress, scope, and sourceId) and another with one field (addressLookup).
The first record type defines an address range (baseAddress and scope) and an indication which memory manager is responsible for tht memory range.
The second record type, which has only one field, which defines a dynamically-sized list of MemorySource elements.

The third datatype is an algebraic datatype defining the kind of messages that can be send to the memory manager: registering a memory range, reading, and writing.

The fourth datatype is a singleton datatype, which will act as the label/name for the interface defining our memory manager.

We now start defining the actual behaviour of our memory manager, starting with its type annotation:

memoryManager :: MemState -> Input MemCommand -> Sim MemState

The type definition tells us that the first argument has the type of our internal component state, and the second argument a value of type Input a, where the a is instantiate to the MemCommand datatype.
The possible values of the Input a type are enumerated in the OS Component API section.
The value of the result is of type Sim MemState.
This tells us two things:

The memoryManager function is executed within the Sim monad.

The actual value that is returned is of type MemState.

A monad is many wonderful things [2], way too much to explain here, so for the rest of this README we see it as an execution environment.
Only inside this execution environment will we have access to the SoOSiM API functions.

Although we know the types of the arguments and the result of the function, we don't know their actual meaning.
The SoOSiM simulator will call your component behaviour, passing as the first argument its current internal state.
The second argument is an event that triggered the execution of your component: for example a message send to you by another component.
The result that you must ultimately return is the, potentially updated, internal state of your component.

We now turn to the first line of the actual function definition:

memoryManager s (Message content retAddr) = do

Where memoryManager is the name of the function, s the first argument (of type MemState).
We pattern-match on the second argument, meaning this function definition clause only works for values whose constructor is Message.
By pattern matching we get access to the fields of the datatype, where we bind the names content and retAddr to the values of these fields.

The dokeyword after the = sign indicates that the function executes within a monadic environment, the Sim environment in our case.
The semantics in a monadic environment are different from those in a normal Haskell functions.
A monadic environment has a more imperative feel, in which your function definition interacts with the environment step-by-step, statement after statements.
This also gives rise to the scoping rules familiar to the imperative programmer: names cannot be used before they are declared.

Next we define a nested case-statement that contains most of the actual behaviour of our memory manager component:

In the first alternative of our case-statement we handle a Register message, by updating our address lookup table with an additional memory source.
We yield to the simulator with our updated internal state.

In the second alternative we handle a Read request.
The next line in our function definition, which checks which specific memory manager is responsible for the address, is:

let src = checkAddress (addressLookup s) addr

Haskell is white-space sensitive, so make sure that you have a good editor that does automatic indenting.
We use the let construct to bind the expression checkAddress (addressLookup s) addr to the name src.
We use these let-bindings to bind pure expressions to names, where pure means that the expression has no side-effects [3].
We can now just use the name src instead of having to type checkAddress (addressLookup s) addr every time.
Don't worry about efficiency, the evaluation mechanics of Haskell will ensure that the actual expression is only calculated once, even when we use the src name multiple times.

In the next case-statement we check if the current or a remote memory manager is responsible for handling the address.
In either alternative we must use the do keyword again because we will be executing multiple statements.
We will now finally use some of the API functions, the first we encounter is:

addrVal <- readMemory addr

The readMemory function accesses the simulator environment, retrieving the value of the memory location specified by addr.
We use the left-arrow <- to indicate that this is a side-effecting expression (we are accessing the simulator environment), and that addrVal is not bound to the expression itself, but the value belonging to the execution of this statement.

After reading the memory, we send the value back to the module that initially requested the memory access.
We send the read value as a response to the return address (retAddr).
Having serviced the request, we use the yield function to give the (unaltered) internal state back to the simulation environment.

Here we define a so-called type-class instance.
At this moment you do not need to know what a type-class is, just that you need to define this instance if you want your component to be able to be used by the SoOSiM simulator.

We use our singleton datatype, MemoryManager, as the label/name for our ComponentInterface instance.
All (type-)functions in this interface receive the interface label as their first argument.
For the type-functions (such as State s) we must explicitly mention the label, for normal function we can just use the underscore (_) as a place holder.

The instance must always contain the definitions for State, Receive, Send, initState, componentName and componentBehaviour.
The State indicates the datatype representing the internal state of a module.
The Receive indicates the datatype of messages that this component is expecting to receive.
The Send indicates the datatype of messages this component will send as responses to invocation.
The initState function returns a minimal internal state of your component.
The componentName is a function returning the globally unique name of your component.
Finally componentBehaviour is a function returning the behaviour of your component.
The behaviour of your component must always have the type:

(State iface) -> Input (Receive iface) -> Sim (State iface)

Where State iface is the datatype of your component's internal state, and Receive iface is the datetype of the type of messages the component handles.

Simulator Events

data Input a
= Message a ReturnAddress -- ^ A message send by another component: the
-- first field is the message content, the
-- second field is the address to send
-- responses to
| Tick -- ^ Event send every simulation round

Handling Dynamic Values

-- | Converts a 'Dynamic' object back into an ordinary Haskell value of the
-- correct type.
unmarshall ::
Typeable a
=> Dynamic -- ^ The dynamically-typed object
-> a -- ^ Returns: the value of the first argument, if it has the
-- correct type, otherwise it gives an error.

References

[1] Here is a chapter from a book that introduces the correspondence between Haskell types and C types:
http://book.realworldhaskell.org/read/defining-types-streamlining-functions.html

[2] Some resources that discuss monads: http://book.realworldhaskell.org/read/monads.html and http://learnyouahaskell.com/a-fistful-of-monads

[3] A more elaborate explanation of purity can be found here: http://learnyouahaskell.com/introduction#so-whats-haskell