From gem5
Revision as of 10:59, 1 April 2011 by Nilayvaish (talk | contribs) (Protocol State Machines: Added structure keyword's explanation)
Jump to: navigation, search

SLICC is a domain specific language for specifying cache coherence protocols. The SLICC compiler generates C++ code for different controllers, which can work in tandem with other parts of Ruby. The compiler also generates an HTML specification of the protocol.

Input To the Compiler

The SLICC compiler takes as input files that specify the controllers involved in the protocol. The .slicc file specifies the different files used by the particular protocol under consideration. For example, if trying to specify the MI protocol using SLICC, then we may ues MI.slicc as the file that specifies all the files necessary for the protocol. The files necessary for specifying a protocol include the definitions of the state machines for different controllers, and of the network messages that are passed on between these controllers.

The files have a syntax similar to that of C++. The compiler, written using PLY (Python Lex-Yacc), parses these files to create an Abstract Syntax Tree (AST). The AST is then traversed to build some of the internal data structures. Finally the compiler outputs the C++ code by traversing the tree again. The AST represents the hierarchy of different structures present with in a state machine. We describe these structures next.

Protocol State Machines

In this section we take a closer look at what goes in to a file containing specification of a state machine. Each state machine is described using SLICC's machine datatype. Each machine has several different types of members. Machines for cache and directory controllers include cache memory and directory memory data members respectively. We will use the MI protocol available in src/mem/protocol as our running example. So here is how you might want to start writing a state machine

 machine(L1Cache, "MI Example L1 Cache")
  : Sequencer * sequencer,
    CacheMemory * cacheMemory,
    int cache_response_latency = 12,
    int issue_latency = 2 {
      // Add rest of the stuff

  • In order to let the controller receive messages from different entities in the system, the machine has a number of Message Buffers. These act as input and output ports for the machine. Here is an example specifying the output ports.
 MessageBuffer requestFromCache, network="To", virtual_network="2", ordered="true";
 MessageBuffer responseFromCache, network="To", virtual_network="4", ordered="true";

Note that Message Buffers have some attributes that need to be specified correctly. Another example, this time for specifying the input ports.

 MessageBuffer forwardToCache, network="From", virtual_network="3", ordered="true";
 MessageBuffer responseToCache, network="From", virtual_network="4", ordered="true";

  • Next the machine includes a declaration of the states that machine can possibly reach. In cache coherence protocol, states can be of two types -- stable and transient. A cache block is said to be in a stable state if in the absence of any activity (in coming request for the block from another controller, for example), the cache block would remain in that state for ever. Transient states are required for transitioning between stable states. They are needed when ever the transition between two stable states can not be done in an atomic fashion. Next is an example that shows how states are declared. SLICC has a keyword state_declaration that has to be used for declaring states.
 state_declaration(State, desc="Cache states") {
   I, AccessPermission:Invalid, desc="Not Present/Invalid";
   II, AccessPermission:Busy, desc="Not Present/Invalid, issued PUT";
   M, AccessPermission:Read_Write, desc="Modified";
   MI, AccessPermission:Busy, desc="Modified, issued PUT";
   MII, AccessPermission:Busy, desc="Modified, issued PUTX, received nack";
   IS, AccessPermission:Busy, desc="Issued request for LOAD/IFETCH";
   IM, AccessPermission:Busy, desc="Issued request for STORE/ATOMIC";

The states I and M are the only stable states in this example. Again note that certain attributes have to be specified with the states.

  • The state machine needs to specify the events it can handle and thus transition from one state to another. SLICC provides the keyword enumeration which can be used for specifying the set of possible events. An example to shed more light on this -
 enumeration(Event, desc="Cache events") {
   // From processor
   Load,       desc="Load request from processor";
   Ifetch,     desc="Ifetch request from processor";
   Store,      desc="Store request from processor";
   Data,       desc="Data from network";
   Fwd_GETX,        desc="Forward from network";
   Inv,        desc="Invalidate request from dir";
   Replacement,  desc="Replace a block";
   Writeback_Ack,   desc="Ack from the directory for a writeback";
   Writeback_Nack,   desc="Nack from the directory for a writeback";

  • While developing a protocol machine, we may need to define structures that represent different entities in a memory system. SLICC provides the keyword structure for this purpose. An example follows
 structure(Entry, desc="...", interface="AbstractCacheEntry") {
   State CacheState,        desc="cache state";
   bool Dirty,              desc="Is the data dirty (different than memory)?";
   DataBlock DataBlk,       desc="Data in the block";

The cool thing about using SLICC's structure is that it automatically generates for you the get and set functions on different fields. It also writes a nice print function and overloads the << operator. But in case you would prefer do everything on your own, you can make use of the keyword external in the declaration of the structure. This would prevent SLICC from generating C++ code for this structure.

 structure(TBETable, external="yes") {
   TBE lookup(Address);
   void allocate(Address);
   void deallocate(Address);
   bool isPresent(Address);