1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
|
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.
|