43e4f59e4f
Though OutPort's message type is not used to generate code, this fix checks that the programmer's intent is correct. Eventually, we may want to remove the message type from the OutPort declaration statement. |
||
---|---|---|
.. | ||
ast | ||
doc | ||
generate | ||
symbols | ||
__init__.py | ||
main.py | ||
parser.py | ||
README | ||
util.py |
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.