| Overview | |
| ======== | |
| This is SLICC, a domain specific language to specify cache coherence protocol | |
| we have developed in Multifacet group. | |
| It is developed by Milo Martin <milo@cs.wisc.edu> | |
| This document is prepared by Min Xu <mxu@cae.wisc.edu> while I am learning the | |
| system. With minor correctness updates by Brad Beckmann <beckmann@cs.wisc.edu> | |
| It can be used to generate C++ code that works with RUBY cache simulator as | |
| well as generate HTML and other document to describe the target protocol. | |
| Some user document is available in doc directory. | |
| Tech details | |
| ============ | |
| SLICC take a text input with similar syntax to C++ language and use the lexer | |
| and parser in parser directory to construct a Abstract Syntax Tree (AST) | |
| internally. After having done this first pass, the AST is traversed to fill | |
| several interval table, such as symbol table, type table, etc. Finally the code | |
| is generated by traversing the tree once again. | |
| Note, by Milo's good coding habit, almost all C++ class define their private | |
| copy/assignment constructor. This prevents accidentally copying/assigning an | |
| object by its address. | |
| The AST basically looks like a hierarchical representation of the text input. | |
| At the highest level, it has the "Machine", each Machine has several "states" | |
| and "events" and "actions" and "transistions". | |
| Since the language is domain specific, many assumptions of the target system is | |
| hardcoded in SLICC. For example, ruby would expect the generated code for each | |
| system node, has the following components: | |
| processor(sequencer, not generated?) | |
| cache | |
| directory (with memory block value, only when compiled with tester) | |
| network interface (NI) | |
| Directory generator/ contains routines to generate HTML/MIF format output. | |
| fileio.[Ch] has a routine to conditionally write a file only when the original | |
| content of the file is different from what is going to be written, this avoid | |
| re-make those file after regenerate the protocol. html_gen.[Ch] contains the | |
| symbol name munge and index page generation. mif_gen.[Ch] contains the entire | |
| MIF output generation routine, mainly a table buildup. | |
| Directory symbol/ contains classes to represent symbols in the slicc input | |
| file. Base class is "Symbol". Derived subclasses are "Action Event Func State | |
| StateMachine Transition Type Var". "Symbol" has knowledge about its locations | |
| in the source file and short name, long name. "SymbolTable" is a list of | |
| symbols and g_sym_table is the global SymbolTable of the slicc system. | |
| One can query a SymbolTable by symbol's id. Also SymbolTable is responsible for | |
| keeping track of Symbol's declaration in correct scope. The current | |
| implementation uses a stack which dynamically determine the scope of symbol | |
| lookups. Global scope is at bottom of the stack (vector[0]). SymbolTable is | |
| also the main place to write out the generated C++/HTML/MIF files. | |
| SymbolTable::writeNodeFiles() is one of the place to look for hardcoded C++ | |
| code for node.[Ch]. And Type.[Ch] is the place where generating enumeration and | |
| Message/NetworkMessage declaration and implementation. Func.[Ch] is used to | |
| generate function of the class Chip. StateMachine.[Ch] wrap the whole thing | |
| up by putting States, Actions, Events together. It actually has a two dimension | |
| table like the one represented in the HTML output. Actions are indexed with | |
| the initial state and observed event. After the tabel being built, the | |
| StateMachine class can write out Transitions/Controller/wakeup_logic into C++ | |
| outputs. Finally, in symbol directory, Var.[Ch] seem to incomplete? | |
| Demystify all those "predefined" external types, like "Address". Where are | |
| they defined? They are in ../protocol/RubySlicc-*.sm and | |
| ../protocol/RubySlicc_interfaces.slicc is include in the slicc invocation | |
| command in ../ruby/Makefile. | |
| Another myth: "trigger" method is hardcoded in ast/InPortDeclAST.C and | |
| ast/FuncCallExprAST.C. The function is similar to inlined function in the | |
| output generated code, so you cannot find any occurance of string "trigger" in | |
| the generated code. "trigger" also increment a counter that is checked every | |
| time a transition is done. In one ruby cycle, only TRANSITIONS_PER_RUBY_CYCLE | |
| number of transitions can be done. ast/FuncCallExprAST.C also contains some | |
| code for function "error" and "assert" and "DEBUG_EXPR", all in the same | |
| manner. Ruby always issues transitions from the first port while there is any. | |
| Stalled transition in Ruby does not consume a sub-cycle. This models the | |
| hardware that probe all port in parallel, pick one transition from the highest | |
| priority queue if the transistion was not stalled by any resources constraint. | |
| Another note: scheduleEvent() call of ruby make sure a consumer is woken up at | |
| specified cycle, and only once per cycle. | |
| Action z_stall, where is it? It is hardcoded in symbols/StateMachine.C. In | |
| function StateMachine::printCSwitch(), z_stall cause the generated code return | |
| TransitionResult_ProtocolStall. Also the HTML output for z_stall has to be | |
| consequently hardcoded. I am not sure that's really a good idea or not. :-) | |
| Question: How comes there is no "for" loop statement in slicc? | |
| Answer: Been there, done that. That is easy to add, first of all. But unbound | |
| loop make slicc eventually un-synthesizable. We want to avoid that. If you want | |
| to loop through a bounded array do something, make the action done in a | |
| external interface in RubySlicc_Util.h. Inside, you just pass the vector as | |
| parameter to the external interface to achieve the same effects. | |
| Another bad thing of using loop statement like for is that we can not determine | |
| how many buffer space to allocate before the transition. With a vector, if it | |
| easy to understand we can always allocate the worst case number of hardware | |
| resources. | |
| Question: Wait! It seems statement check_allocate does nothing! | |
| Answer: No, it does call areNSoltsAvailable() function of the object before any | |
| statement is executed in one action. It does *NOT* generate code in its | |
| original place in the code, instead, it scan the body of the action code and | |
| determine how many slots are needed to allocated before hand. So the | |
| transaction is all done or nothing done. I had tried to make all actions return | |
| boolean values and the false return cause a transition to abort with | |
| ResourceStall. But it is later on deemed to be too flexible in its semantics. | |
| We should never introduce control flow inside the transitions, so that each | |
| transition is either "all" or "nothing". Just that simple. BTW, if you call | |
| check_allocate twice, areNSoltsAvailable(2) is generated, three times generates | |
| areNSoltsAvailable(3), etc. |