blob: 5d315e98bf68b2f68260ff779069100143763e25 [file] [log] [blame]
# Copyright (c) 2019-2021 ARM Limited
# All rights reserved.
#
# The license below extends only to copyright in the software and shall
# not be construed as granting a license to any other intellectual
# property including but not limited to intellectual property relating
# to a hardware implementation of the functionality of the software
# licensed hereunder. You may use the software subject to the license
# terms below provided that you ensure that this notice is replicated
# unmodified and in its entirety in all distributions of the software,
# modified or unmodified, in source code or in binary form.
#
# Copyright (c) 1999-2008 Mark D. Hill and David A. Wood
# Copyright (c) 2009 The Hewlett-Packard Development Company
# Copyright (c) 2013 Advanced Micro Devices, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met: redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer;
# redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution;
# neither the name of the copyright holders nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from collections import OrderedDict
from slicc.symbols.Symbol import Symbol
from slicc.symbols.Var import Var
import slicc.generate.html as html
import re
python_class_map = {
"int": "Int",
"NodeID": "Int",
"uint32_t" : "UInt32",
"std::string": "String",
"bool": "Bool",
"CacheMemory": "RubyCache",
"WireBuffer": "RubyWireBuffer",
"Sequencer": "RubySequencer",
"HTMSequencer": "RubyHTMSequencer",
"GPUCoalescer" : "RubyGPUCoalescer",
"VIPERCoalescer" : "VIPERCoalescer",
"DirectoryMemory": "RubyDirectoryMemory",
"PerfectCacheMemory": "RubyPerfectCacheMemory",
"MemoryControl": "MemoryControl",
"MessageBuffer": "MessageBuffer",
"DMASequencer": "DMASequencer",
"RubyPrefetcher":"RubyPrefetcher",
"Cycles":"Cycles",
}
class StateMachine(Symbol):
def __init__(self, symtab, ident, location, pairs, config_parameters):
super().__init__(symtab, ident, location, pairs)
self.table = None
# Data members in the State Machine that have been declared before
# the opening brace '{' of the machine. Note that these along with
# the members in self.objects form the entire set of data members.
self.config_parameters = config_parameters
self.prefetchers = []
for param in config_parameters:
if param.pointer:
var = Var(symtab, param.ident, location, param.type_ast.type,
"(*m_%s_ptr)" % param.ident, {}, self)
else:
var = Var(symtab, param.ident, location, param.type_ast.type,
"m_%s" % param.ident, {}, self)
self.symtab.registerSym(param.ident, var)
if str(param.type_ast.type) == "RubyPrefetcher":
self.prefetchers.append(var)
self.states = OrderedDict()
self.events = OrderedDict()
self.actions = OrderedDict()
self.request_types = OrderedDict()
self.transitions = []
self.in_ports = []
self.functions = []
# Data members in the State Machine that have been declared inside
# the {} machine. Note that these along with the config params
# form the entire set of data members of the machine.
self.objects = []
self.TBEType = None
self.EntryType = None
self.debug_flags = set()
self.debug_flags.add('RubyGenerated')
self.debug_flags.add('RubySlicc')
def __repr__(self):
return "[StateMachine: %s]" % self.ident
def addState(self, state):
assert self.table is None
self.states[state.ident] = state
def addEvent(self, event):
assert self.table is None
self.events[event.ident] = event
def addAction(self, action):
assert self.table is None
# Check for duplicate action
for other in self.actions.values():
if action.ident == other.ident:
action.warning("Duplicate action definition: %s" % action.ident)
action.error("Duplicate action definition: %s" % action.ident)
if action.short == other.short:
other.warning("Duplicate action shorthand: %s" % other.ident)
other.warning(" shorthand = %s" % other.short)
action.warning("Duplicate action shorthand: %s" % action.ident)
action.error(" shorthand = %s" % action.short)
self.actions[action.ident] = action
def addDebugFlag(self, flag):
self.debug_flags.add(flag)
def addRequestType(self, request_type):
assert self.table is None
self.request_types[request_type.ident] = request_type
def addTransition(self, trans):
assert self.table is None
self.transitions.append(trans)
def addInPort(self, var):
self.in_ports.append(var)
def addFunc(self, func):
# register func in the symbol table
self.symtab.registerSym(str(func), func)
self.functions.append(func)
def addObject(self, obj):
self.symtab.registerSym(str(obj), obj)
self.objects.append(obj)
def addType(self, type):
type_ident = '%s' % type.c_ident
if type_ident == "%s_TBE" %self.ident:
if self.TBEType != None:
self.error("Multiple Transaction Buffer types in a " \
"single machine.");
self.TBEType = type
elif "interface" in type and "AbstractCacheEntry" == type["interface"]:
if "main" in type and "false" == type["main"].lower():
pass # this isn't the EntryType
else:
if self.EntryType != None:
self.error("Multiple AbstractCacheEntry types in a " \
"single machine.");
self.EntryType = type
# Needs to be called before accessing the table
def buildTable(self):
assert self.table is None
table = {}
for trans in self.transitions:
# Track which actions we touch so we know if we use them
# all -- really this should be done for all symbols as
# part of the symbol table, then only trigger it for
# Actions, States, Events, etc.
for action in trans.actions:
action.used = True
index = (trans.state, trans.event)
if index in table:
table[index].warning("Duplicate transition: %s" % table[index])
trans.error("Duplicate transition: %s" % trans)
table[index] = trans
# Look at all actions to make sure we used them all
for action in self.actions.values():
if not action.used:
error_msg = "Unused action: %s" % action.ident
if "desc" in action:
error_msg += ", " + action.desc
action.warning(error_msg)
self.table = table
# determine the port->msg buffer mappings
def getBufferMaps(self, ident):
msg_bufs = []
port_to_buf_map = {}
in_msg_bufs = {}
for port in self.in_ports:
buf_name = "m_%s_ptr" % port.pairs["buffer_expr"].name
msg_bufs.append(buf_name)
port_to_buf_map[port] = msg_bufs.index(buf_name)
if buf_name not in in_msg_bufs:
in_msg_bufs[buf_name] = [port]
else:
in_msg_bufs[buf_name].append(port)
return port_to_buf_map, in_msg_bufs, msg_bufs
def writeCodeFiles(self, path, includes):
self.printControllerPython(path)
self.printControllerHH(path)
self.printControllerCC(path, includes)
self.printCSwitch(path)
self.printCWakeup(path, includes)
def printControllerPython(self, path):
code = self.symtab.codeFormatter()
ident = self.ident
py_ident = "%s_Controller" % ident
c_ident = "%s_Controller" % self.ident
code('''
from m5.params import *
from m5.SimObject import SimObject
from m5.objects.Controller import RubyController
class $py_ident(RubyController):
type = '$py_ident'
cxx_header = 'mem/ruby/protocol/${c_ident}.hh'
cxx_class = 'gem5::ruby::$py_ident'
''')
code.indent()
for param in self.config_parameters:
dflt_str = ''
if param.rvalue is not None:
dflt_str = str(param.rvalue.inline()) + ', '
if param.type_ast.type.c_ident in python_class_map:
python_type = python_class_map[param.type_ast.type.c_ident]
code('${{param.ident}} = Param.${{python_type}}(${dflt_str}"")')
else:
self.error("Unknown c++ to python class conversion for c++ " \
"type: '%s'. Please update the python_class_map " \
"in StateMachine.py", param.type_ast.type.c_ident)
code.dedent()
code.write(path, '%s.py' % py_ident)
def printControllerHH(self, path):
'''Output the method declarations for the class declaration'''
code = self.symtab.codeFormatter()
ident = self.ident
c_ident = "%s_Controller" % self.ident
code('''
// Created by slicc definition of Module "${{self.short}}"
#ifndef __${ident}_CONTROLLER_HH__
#define __${ident}_CONTROLLER_HH__
#include <iostream>
#include <sstream>
#include <string>
#include "mem/ruby/common/Consumer.hh"
#include "mem/ruby/protocol/TransitionResult.hh"
#include "mem/ruby/protocol/Types.hh"
#include "mem/ruby/slicc_interface/AbstractController.hh"
#include "params/$c_ident.hh"
''')
seen_types = set()
for var in self.objects:
if var.type.ident not in seen_types and not var.type.isPrimitive:
code('#include "mem/ruby/protocol/${{var.type.c_ident}}.hh"')
seen_types.add(var.type.ident)
# for adding information to the protocol debug trace
code('''
namespace gem5
{
namespace ruby
{
extern std::stringstream ${ident}_transitionComment;
class $c_ident : public AbstractController
{
public:
typedef ${c_ident}Params Params;
$c_ident(const Params &p);
static int getNumControllers();
void init();
MessageBuffer *getMandatoryQueue() const;
MessageBuffer *getMemReqQueue() const;
MessageBuffer *getMemRespQueue() const;
void initNetQueues();
void print(std::ostream& out) const;
void wakeup();
void resetStats();
void regStats();
void collateStats();
void recordCacheTrace(int cntrl, CacheRecorder* tr);
Sequencer* getCPUSequencer() const;
DMASequencer* getDMASequencer() const;
GPUCoalescer* getGPUCoalescer() const;
bool functionalReadBuffers(PacketPtr&);
bool functionalReadBuffers(PacketPtr&, WriteMask&);
int functionalWriteBuffers(PacketPtr&);
void countTransition(${ident}_State state, ${ident}_Event event);
void possibleTransition(${ident}_State state, ${ident}_Event event);
uint64_t getEventCount(${ident}_Event event);
bool isPossible(${ident}_State state, ${ident}_Event event);
uint64_t getTransitionCount(${ident}_State state, ${ident}_Event event);
private:
''')
code.indent()
# added by SS
for param in self.config_parameters:
if param.pointer:
code('${{param.type_ast.type}}* m_${{param.ident}}_ptr;')
else:
code('${{param.type_ast.type}} m_${{param.ident}};')
code('''
TransitionResult doTransition(${ident}_Event event,
''')
if self.EntryType != None:
code('''
${{self.EntryType.c_ident}}* m_cache_entry_ptr,
''')
if self.TBEType != None:
code('''
${{self.TBEType.c_ident}}* m_tbe_ptr,
''')
code('''
Addr addr);
TransitionResult doTransitionWorker(${ident}_Event event,
${ident}_State state,
${ident}_State& next_state,
''')
if self.TBEType != None:
code('''
${{self.TBEType.c_ident}}*& m_tbe_ptr,
''')
if self.EntryType != None:
code('''
${{self.EntryType.c_ident}}*& m_cache_entry_ptr,
''')
code('''
Addr addr);
${ident}_Event m_curTransitionEvent;
${ident}_State m_curTransitionNextState;
${ident}_Event curTransitionEvent() { return m_curTransitionEvent; }
${ident}_State curTransitionNextState() { return m_curTransitionNextState; }
int m_counters[${ident}_State_NUM][${ident}_Event_NUM];
int m_event_counters[${ident}_Event_NUM];
bool m_possible[${ident}_State_NUM][${ident}_Event_NUM];
static std::vector<statistics::Vector *> eventVec;
static std::vector<std::vector<statistics::Vector *> > transVec;
static int m_num_controllers;
// Internal functions
''')
for func in self.functions:
proto = func.prototype
if proto:
code('$proto')
if self.EntryType != None:
code('''
// Set and Reset for cache_entry variable
void set_cache_entry(${{self.EntryType.c_ident}}*& m_cache_entry_ptr, AbstractCacheEntry* m_new_cache_entry);
void unset_cache_entry(${{self.EntryType.c_ident}}*& m_cache_entry_ptr);
''')
if self.TBEType != None:
code('''
// Set and Reset for tbe variable
void set_tbe(${{self.TBEType.c_ident}}*& m_tbe_ptr, ${ident}_TBE* m_new_tbe);
void unset_tbe(${{self.TBEType.c_ident}}*& m_tbe_ptr);
''')
# Prototype the actions that the controller can take
code('''
// Actions
''')
if self.TBEType != None and self.EntryType != None:
for action in self.actions.values():
code('/** \\brief ${{action.desc}} */')
code('void ${{action.ident}}(${{self.TBEType.c_ident}}*& '
'm_tbe_ptr, ${{self.EntryType.c_ident}}*& '
'm_cache_entry_ptr, Addr addr);')
elif self.TBEType != None:
for action in self.actions.values():
code('/** \\brief ${{action.desc}} */')
code('void ${{action.ident}}(${{self.TBEType.c_ident}}*& '
'm_tbe_ptr, Addr addr);')
elif self.EntryType != None:
for action in self.actions.values():
code('/** \\brief ${{action.desc}} */')
code('void ${{action.ident}}(${{self.EntryType.c_ident}}*& '
'm_cache_entry_ptr, Addr addr);')
else:
for action in self.actions.values():
code('/** \\brief ${{action.desc}} */')
code('void ${{action.ident}}(Addr addr);')
# the controller internal variables
code('''
// Objects
''')
for var in self.objects:
th = var.get("template", "")
code('${{var.type.c_ident}}$th* m_${{var.ident}}_ptr;')
code.dedent()
code('''
};
} // namespace ruby
} // namespace gem5
#endif // __${ident}_CONTROLLER_H__
''')
code.write(path, '%s.hh' % c_ident)
def printControllerCC(self, path, includes):
'''Output the actions for performing the actions'''
code = self.symtab.codeFormatter()
ident = self.ident
c_ident = "%s_Controller" % self.ident
# Unfortunately, clang compilers will throw a "call to function ...
# that is neither visible in the template definition nor found by
# argument-dependent lookup" error if "mem/ruby/common/BoolVec.hh" is
# included after "base/cprintf.hh". This is because "base/cprintf.hh"
# utilizes a "<<" operator in "base/cprintf_formats.hh" that is
# defined in "mem/ruby/common/BoolVec.hh". While GCC compilers permit
# the operator definition after usage in this case, clang compilers do
# not.
#
# The reason for this verbose solution below is due to the gem5
# style-checker, which will complain if "mem/ruby/common/BoolVec.hh"
# is included above "base/cprintf.hh" in this file, despite it being
# necessary in this case. This is therefore a bit of a hack to keep
# both clang and our style-checker happy.
base_include = '''
#include "base/compiler.hh"
#include "base/cprintf.hh"
'''
boolvec_include = '''
#include "mem/ruby/common/BoolVec.hh"
'''
code('''
// Created by slicc definition of Module "${{self.short}}"
#include <sys/types.h>
#include <unistd.h>
#include <cassert>
#include <sstream>
#include <string>
#include <typeinfo>
''')
code(boolvec_include)
code(base_include)
for f in self.debug_flags:
code('#include "debug/${{f}}.hh"')
code('''
#include "mem/ruby/network/Network.hh"
#include "mem/ruby/protocol/${ident}_Controller.hh"
#include "mem/ruby/protocol/${ident}_Event.hh"
#include "mem/ruby/protocol/${ident}_State.hh"
#include "mem/ruby/protocol/Types.hh"
#include "mem/ruby/system/RubySystem.hh"
''')
for include_path in includes:
code('#include "${{include_path}}"')
# include object classes
seen_types = set()
for var in self.objects:
if var.type.ident not in seen_types and not var.type.isPrimitive:
code('#include "mem/ruby/protocol/${{var.type.c_ident}}.hh"')
seen_types.add(var.type.ident)
num_in_ports = len(self.in_ports)
code('''
namespace gem5
{
namespace ruby
{
int $c_ident::m_num_controllers = 0;
std::vector<statistics::Vector *> $c_ident::eventVec;
std::vector<std::vector<statistics::Vector *> > $c_ident::transVec;
// for adding information to the protocol debug trace
std::stringstream ${ident}_transitionComment;
#ifndef NDEBUG
#define APPEND_TRANSITION_COMMENT(str) (${ident}_transitionComment << str)
#else
#define APPEND_TRANSITION_COMMENT(str) do {} while (0)
#endif
/** \\brief constructor */
$c_ident::$c_ident(const Params &p)
: AbstractController(p)
{
m_machineID.type = MachineType_${ident};
m_machineID.num = m_version;
m_num_controllers++;
p.ruby_system->registerAbstractController(this);
m_in_ports = $num_in_ports;
''')
code.indent()
#
# After initializing the universal machine parameters, initialize the
# this machines config parameters. Also if these configuration params
# include a sequencer, connect the it to the controller.
#
for param in self.config_parameters:
if param.pointer:
code('m_${{param.ident}}_ptr = p.${{param.ident}};')
else:
code('m_${{param.ident}} = p.${{param.ident}};')
if re.compile("sequencer").search(param.ident) or \
param.type_ast.type.c_ident == "GPUCoalescer" or \
param.type_ast.type.c_ident == "VIPERCoalescer":
code('''
if (m_${{param.ident}}_ptr != NULL) {
m_${{param.ident}}_ptr->setController(this);
}
''')
code('''
for (int state = 0; state < ${ident}_State_NUM; state++) {
for (int event = 0; event < ${ident}_Event_NUM; event++) {
m_possible[state][event] = false;
m_counters[state][event] = 0;
}
}
for (int event = 0; event < ${ident}_Event_NUM; event++) {
m_event_counters[event] = 0;
}
''')
code.dedent()
code('''
}
void
$c_ident::initNetQueues()
{
MachineType machine_type = string_to_MachineType("${{self.ident}}");
[[maybe_unused]] int base = MachineType_base_number(machine_type);
''')
code.indent()
# set for maintaining the vnet, direction pairs already seen for this
# machine. This map helps in implementing the check for avoiding
# multiple message buffers being mapped to the same vnet.
vnet_dir_set = set()
for var in self.config_parameters:
vid = "m_%s_ptr" % var.ident
if "network" in var:
vtype = var.type_ast.type
code('assert($vid != NULL);')
# Network port object
network = var["network"]
if "virtual_network" in var:
vnet = var["virtual_network"]
vnet_type = var["vnet_type"]
assert (vnet, network) not in vnet_dir_set
vnet_dir_set.add((vnet,network))
code('''
m_net_ptr->set${network}NetQueue(m_version + base, $vid->getOrdered(), $vnet,
"$vnet_type", $vid);
''')
# Set Priority
if "rank" in var:
code('$vid->setPriority(${{var["rank"]}})')
code.dedent()
code('''
}
void
$c_ident::init()
{
// initialize objects
''')
code.indent()
for var in self.objects:
vtype = var.type
vid = "m_%s_ptr" % var.ident
if "network" not in var:
# Not a network port object
if "primitive" in vtype:
code('$vid = new ${{vtype.c_ident}};')
if "default" in var:
code('(*$vid) = ${{var["default"]}};')
else:
# Normal Object
th = var.get("template", "")
expr = "%s = new %s%s" % (vid, vtype.c_ident, th)
args = ""
if "non_obj" not in vtype and not vtype.isEnumeration:
args = var.get("constructor", "")
code('$expr($args);')
code('assert($vid != NULL);')
if "default" in var:
code('*$vid = ${{var["default"]}}; // Object default')
elif "default" in vtype:
comment = "Type %s default" % vtype.ident
code('*$vid = ${{vtype["default"]}}; // $comment')
# Set the prefetchers
code()
for prefetcher in self.prefetchers:
code('${{prefetcher.code}}.setController(this);')
code()
for port in self.in_ports:
# Set the queue consumers
code('${{port.code}}.setConsumer(this);')
# Initialize the transition profiling
code()
for trans in self.transitions:
# Figure out if we stall
stall = False
for action in trans.actions:
if action.ident == "z_stall":
stall = True
# Only possible if it is not a 'z' case
if not stall:
state = "%s_State_%s" % (self.ident, trans.state.ident)
event = "%s_Event_%s" % (self.ident, trans.event.ident)
code('possibleTransition($state, $event);')
code.dedent()
code('''
AbstractController::init();
resetStats();
}
''')
mq_ident = "NULL"
for port in self.in_ports:
if port.code.find("mandatoryQueue_ptr") >= 0:
mq_ident = "m_mandatoryQueue_ptr"
memoutq_ident = "NULL"
for param in self.config_parameters:
if param.ident.find("requestToMemory") >= 0:
memoutq_ident = "m_requestToMemory_ptr"
memq_ident = "NULL"
for port in self.in_ports:
if port.code.find("responseFromMemory_ptr") >= 0:
memq_ident = "m_responseFromMemory_ptr"
seq_ident = "NULL"
for param in self.config_parameters:
if param.ident == "sequencer":
assert(param.pointer)
seq_ident = "m_%s_ptr" % param.ident
dma_seq_ident = "NULL"
for param in self.config_parameters:
if param.ident == "dma_sequencer":
assert(param.pointer)
dma_seq_ident = "m_%s_ptr" % param.ident
coal_ident = "NULL"
for param in self.config_parameters:
if param.ident == "coalescer":
assert(param.pointer)
coal_ident = "m_%s_ptr" % param.ident
if seq_ident != "NULL":
code('''
Sequencer*
$c_ident::getCPUSequencer() const
{
if (NULL != $seq_ident && $seq_ident->isCPUSequencer()) {
return $seq_ident;
} else {
return NULL;
}
}
''')
else:
code('''
Sequencer*
$c_ident::getCPUSequencer() const
{
return NULL;
}
''')
if dma_seq_ident != "NULL":
code('''
DMASequencer*
$c_ident::getDMASequencer() const
{
if (NULL != $dma_seq_ident) {
return $dma_seq_ident;
} else {
return NULL;
}
}
''')
else:
code('''
DMASequencer*
$c_ident::getDMASequencer() const
{
return NULL;
}
''')
if coal_ident != "NULL":
code('''
GPUCoalescer*
$c_ident::getGPUCoalescer() const
{
if (NULL != $coal_ident && !$coal_ident->isCPUSequencer()) {
return $coal_ident;
} else {
return NULL;
}
}
''')
else:
code('''
GPUCoalescer*
$c_ident::getGPUCoalescer() const
{
return NULL;
}
''')
code('''
void
$c_ident::regStats()
{
AbstractController::regStats();
// For each type of controllers, one controller of that type is picked
// to aggregate stats of all controllers of that type.
if (m_version == 0) {
Profiler *profiler = params().ruby_system->getProfiler();
statistics::Group *profilerStatsPtr = &profiler->rubyProfilerStats;
for (${ident}_Event event = ${ident}_Event_FIRST;
event < ${ident}_Event_NUM; ++event) {
std::string stat_name =
"${c_ident}." + ${ident}_Event_to_string(event);
statistics::Vector *t =
new statistics::Vector(profilerStatsPtr, stat_name.c_str());
t->init(m_num_controllers);
t->flags(statistics::pdf | statistics::total |
statistics::oneline | statistics::nozero);
eventVec.push_back(t);
}
for (${ident}_State state = ${ident}_State_FIRST;
state < ${ident}_State_NUM; ++state) {
transVec.push_back(std::vector<statistics::Vector *>());
for (${ident}_Event event = ${ident}_Event_FIRST;
event < ${ident}_Event_NUM; ++event) {
std::string stat_name = "${c_ident}." +
${ident}_State_to_string(state) +
"." + ${ident}_Event_to_string(event);
statistics::Vector *t = new statistics::Vector(
profilerStatsPtr, stat_name.c_str());
t->init(m_num_controllers);
t->flags(statistics::pdf | statistics::total |
statistics::oneline | statistics::nozero);
transVec[state].push_back(t);
}
}
}
for (${ident}_Event event = ${ident}_Event_FIRST;
event < ${ident}_Event_NUM; ++event) {
std::string stat_name =
"outTransLatHist." + ${ident}_Event_to_string(event);
statistics::Histogram* t =
new statistics::Histogram(&stats, stat_name.c_str());
stats.outTransLatHist.push_back(t);
t->init(5);
t->flags(statistics::pdf | statistics::total |
statistics::oneline | statistics::nozero);
statistics::Scalar* r = new statistics::Scalar(&stats,
(stat_name + ".retries").c_str());
stats.outTransLatHistRetries.push_back(r);
r->flags(statistics::nozero);
}
for (${ident}_Event event = ${ident}_Event_FIRST;
event < ${ident}_Event_NUM; ++event) {
std::string stat_name = "inTransLatHist." +
${ident}_Event_to_string(event);
statistics::Scalar* r = new statistics::Scalar(&stats,
(stat_name + ".total").c_str());
stats.inTransLatTotal.push_back(r);
r->flags(statistics::nozero);
r = new statistics::Scalar(&stats,
(stat_name + ".retries").c_str());
stats.inTransLatRetries.push_back(r);
r->flags(statistics::nozero);
stats.inTransLatHist.emplace_back();
for (${ident}_State initial_state = ${ident}_State_FIRST;
initial_state < ${ident}_State_NUM; ++initial_state) {
stats.inTransLatHist.back().emplace_back();
for (${ident}_State final_state = ${ident}_State_FIRST;
final_state < ${ident}_State_NUM; ++final_state) {
std::string stat_name = "inTransLatHist." +
${ident}_Event_to_string(event) + "." +
${ident}_State_to_string(initial_state) + "." +
${ident}_State_to_string(final_state);
statistics::Histogram* t =
new statistics::Histogram(&stats, stat_name.c_str());
stats.inTransLatHist.back().back().push_back(t);
t->init(5);
t->flags(statistics::pdf | statistics::total |
statistics::oneline | statistics::nozero);
}
}
}
}
void
$c_ident::collateStats()
{
for (${ident}_Event event = ${ident}_Event_FIRST;
event < ${ident}_Event_NUM; ++event) {
for (unsigned int i = 0; i < m_num_controllers; ++i) {
RubySystem *rs = params().ruby_system;
std::map<uint32_t, AbstractController *>::iterator it =
rs->m_abstract_controls[MachineType_${ident}].find(i);
assert(it != rs->m_abstract_controls[MachineType_${ident}].end());
(*eventVec[event])[i] =
(($c_ident *)(*it).second)->getEventCount(event);
}
}
for (${ident}_State state = ${ident}_State_FIRST;
state < ${ident}_State_NUM; ++state) {
for (${ident}_Event event = ${ident}_Event_FIRST;
event < ${ident}_Event_NUM; ++event) {
for (unsigned int i = 0; i < m_num_controllers; ++i) {
RubySystem *rs = params().ruby_system;
std::map<uint32_t, AbstractController *>::iterator it =
rs->m_abstract_controls[MachineType_${ident}].find(i);
assert(it != rs->m_abstract_controls[MachineType_${ident}].end());
(*transVec[state][event])[i] =
(($c_ident *)(*it).second)->getTransitionCount(state, event);
}
}
}
}
void
$c_ident::countTransition(${ident}_State state, ${ident}_Event event)
{
assert(m_possible[state][event]);
m_counters[state][event]++;
m_event_counters[event]++;
}
void
$c_ident::possibleTransition(${ident}_State state,
${ident}_Event event)
{
m_possible[state][event] = true;
}
uint64_t
$c_ident::getEventCount(${ident}_Event event)
{
return m_event_counters[event];
}
bool
$c_ident::isPossible(${ident}_State state, ${ident}_Event event)
{
return m_possible[state][event];
}
uint64_t
$c_ident::getTransitionCount(${ident}_State state,
${ident}_Event event)
{
return m_counters[state][event];
}
int
$c_ident::getNumControllers()
{
return m_num_controllers;
}
MessageBuffer*
$c_ident::getMandatoryQueue() const
{
return $mq_ident;
}
MessageBuffer*
$c_ident::getMemReqQueue() const
{
return $memoutq_ident;
}
MessageBuffer*
$c_ident::getMemRespQueue() const
{
return $memq_ident;
}
void
$c_ident::print(std::ostream& out) const
{
out << "[$c_ident " << m_version << "]";
}
void $c_ident::resetStats()
{
for (int state = 0; state < ${ident}_State_NUM; state++) {
for (int event = 0; event < ${ident}_Event_NUM; event++) {
m_counters[state][event] = 0;
}
}
for (int event = 0; event < ${ident}_Event_NUM; event++) {
m_event_counters[event] = 0;
}
AbstractController::resetStats();
}
''')
if self.EntryType != None:
code('''
// Set and Reset for cache_entry variable
void
$c_ident::set_cache_entry(${{self.EntryType.c_ident}}*& m_cache_entry_ptr, AbstractCacheEntry* m_new_cache_entry)
{
m_cache_entry_ptr = (${{self.EntryType.c_ident}}*)m_new_cache_entry;
}
void
$c_ident::unset_cache_entry(${{self.EntryType.c_ident}}*& m_cache_entry_ptr)
{
m_cache_entry_ptr = 0;
}
''')
if self.TBEType != None:
code('''
// Set and Reset for tbe variable
void
$c_ident::set_tbe(${{self.TBEType.c_ident}}*& m_tbe_ptr, ${{self.TBEType.c_ident}}* m_new_tbe)
{
m_tbe_ptr = m_new_tbe;
}
void
$c_ident::unset_tbe(${{self.TBEType.c_ident}}*& m_tbe_ptr)
{
m_tbe_ptr = NULL;
}
''')
code('''
void
$c_ident::recordCacheTrace(int cntrl, CacheRecorder* tr)
{
''')
#
# Record cache contents for all associated caches.
#
code.indent()
for param in self.config_parameters:
if param.type_ast.type.ident == "CacheMemory":
assert(param.pointer)
code('m_${{param.ident}}_ptr->recordCacheContents(cntrl, tr);')
code.dedent()
code('''
}
// Actions
''')
if self.TBEType != None and self.EntryType != None:
for action in self.actions.values():
if "c_code" not in action:
continue
code('''
/** \\brief ${{action.desc}} */
void
$c_ident::${{action.ident}}(${{self.TBEType.c_ident}}*& m_tbe_ptr, ${{self.EntryType.c_ident}}*& m_cache_entry_ptr, Addr addr)
{
DPRINTF(RubyGenerated, "executing ${{action.ident}}\\n");
try {
${{action["c_code"]}}
} catch (const RejectException & e) {
fatal("Error in action ${{ident}}:${{action.ident}}: "
"executed a peek statement with the wrong message "
"type specified. ");
}
}
''')
elif self.TBEType != None:
for action in self.actions.values():
if "c_code" not in action:
continue
code('''
/** \\brief ${{action.desc}} */
void
$c_ident::${{action.ident}}(${{self.TBEType.c_ident}}*& m_tbe_ptr, Addr addr)
{
DPRINTF(RubyGenerated, "executing ${{action.ident}}\\n");
${{action["c_code"]}}
}
''')
elif self.EntryType != None:
for action in self.actions.values():
if "c_code" not in action:
continue
code('''
/** \\brief ${{action.desc}} */
void
$c_ident::${{action.ident}}(${{self.EntryType.c_ident}}*& m_cache_entry_ptr, Addr addr)
{
DPRINTF(RubyGenerated, "executing ${{action.ident}}\\n");
${{action["c_code"]}}
}
''')
else:
for action in self.actions.values():
if "c_code" not in action:
continue
code('''
/** \\brief ${{action.desc}} */
void
$c_ident::${{action.ident}}(Addr addr)
{
DPRINTF(RubyGenerated, "executing ${{action.ident}}\\n");
${{action["c_code"]}}
}
''')
for func in self.functions:
code(func.generateCode())
# Function for functional writes to messages buffered in the controller
code('''
int
$c_ident::functionalWriteBuffers(PacketPtr& pkt)
{
int num_functional_writes = 0;
''')
for var in self.objects:
vtype = var.type
if vtype.isBuffer:
vid = "m_%s_ptr" % var.ident
code('num_functional_writes += $vid->functionalWrite(pkt);')
for var in self.config_parameters:
vtype = var.type_ast.type
if vtype.isBuffer:
vid = "m_%s_ptr" % var.ident
code('num_functional_writes += $vid->functionalWrite(pkt);')
code('''
return num_functional_writes;
}
''')
# Function for functional reads to messages buffered in the controller
code('''
bool
$c_ident::functionalReadBuffers(PacketPtr& pkt)
{
''')
for var in self.objects:
vtype = var.type
if vtype.isBuffer:
vid = "m_%s_ptr" % var.ident
code('if ($vid->functionalRead(pkt)) return true;')
for var in self.config_parameters:
vtype = var.type_ast.type
if vtype.isBuffer:
vid = "m_%s_ptr" % var.ident
code('if ($vid->functionalRead(pkt)) return true;')
code('''
return false;
}
bool
$c_ident::functionalReadBuffers(PacketPtr& pkt, WriteMask &mask)
{
bool read = false;
''')
for var in self.objects:
vtype = var.type
if vtype.isBuffer:
vid = "m_%s_ptr" % var.ident
code('if ($vid->functionalRead(pkt, mask)) read = true;')
for var in self.config_parameters:
vtype = var.type_ast.type
if vtype.isBuffer:
vid = "m_%s_ptr" % var.ident
code('if ($vid->functionalRead(pkt, mask)) read = true;')
code('''
return read;
}
} // namespace ruby
} // namespace gem5
''')
code.write(path, "%s.cc" % c_ident)
def printCWakeup(self, path, includes):
'''Output the wakeup loop for the events'''
code = self.symtab.codeFormatter()
ident = self.ident
outputRequest_types = True
if len(self.request_types) == 0:
outputRequest_types = False
code('''
// ${ident}: ${{self.short}}
#include <sys/types.h>
#include <unistd.h>
#include <cassert>
#include <typeinfo>
#include "base/logging.hh"
''')
for f in self.debug_flags:
code('#include "debug/${{f}}.hh"')
code('''
#include "mem/ruby/protocol/${ident}_Controller.hh"
#include "mem/ruby/protocol/${ident}_Event.hh"
#include "mem/ruby/protocol/${ident}_State.hh"
''')
if outputRequest_types:
code('''#include "mem/ruby/protocol/${ident}_RequestType.hh"''')
code('''
#include "mem/ruby/protocol/Types.hh"
#include "mem/ruby/system/RubySystem.hh"
''')
for include_path in includes:
code('#include "${{include_path}}"')
port_to_buf_map, in_msg_bufs, msg_bufs = self.getBufferMaps(ident)
code('''
namespace gem5
{
namespace ruby
{
void
${ident}_Controller::wakeup()
{
if (getMemReqQueue() && getMemReqQueue()->isReady(clockEdge())) {
serviceMemoryQueue();
}
int counter = 0;
while (true) {
unsigned char rejected[${{len(msg_bufs)}}];
memset(rejected, 0, sizeof(unsigned char)*${{len(msg_bufs)}});
// Some cases will put us into an infinite loop without this limit
assert(counter <= m_transitions_per_cycle);
if (counter == m_transitions_per_cycle) {
// Count how often we are fully utilized
stats.fullyBusyCycles++;
// Wakeup in another cycle and try again
scheduleEvent(Cycles(1));
break;
}
''')
code.indent()
code.indent()
# InPorts
#
for port in self.in_ports:
code.indent()
code('// ${ident}InPort $port')
if "rank" in port.pairs:
code('m_cur_in_port = ${{port.pairs["rank"]}};')
else:
code('m_cur_in_port = 0;')
if port in port_to_buf_map:
code('try {')
code.indent()
code('${{port["c_code_in_port"]}}')
if port in port_to_buf_map:
code.dedent()
code('''
} catch (const RejectException & e) {
rejected[${{port_to_buf_map[port]}}]++;
}
''')
code.dedent()
code('')
code.dedent()
code.dedent()
code('''
// If we got this far, we have nothing left todo or something went
// wrong''')
for buf_name, ports in in_msg_bufs.items():
if len(ports) > 1:
# only produce checks when a buffer is shared by multiple ports
code('''
if (${{buf_name}}->isReady(clockEdge()) && rejected[${{port_to_buf_map[ports[0]]}}] == ${{len(ports)}})
{
// no port claimed the message on the top of this buffer
panic("Runtime Error at Ruby Time: %d. "
"All ports rejected a message. "
"You are probably sending a message type to this controller "
"over a virtual network that do not define an in_port for "
"the incoming message type.\\n",
Cycles(1));
}
''')
code('''
break;
}
}
} // namespace ruby
} // namespace gem5
''')
code.write(path, "%s_Wakeup.cc" % self.ident)
def printCSwitch(self, path):
'''Output switch statement for transition table'''
code = self.symtab.codeFormatter()
ident = self.ident
code('''
// ${ident}: ${{self.short}}
#include <cassert>
#include "base/logging.hh"
#include "base/trace.hh"
#include "debug/ProtocolTrace.hh"
#include "debug/RubyGenerated.hh"
#include "mem/ruby/protocol/${ident}_Controller.hh"
#include "mem/ruby/protocol/${ident}_Event.hh"
#include "mem/ruby/protocol/${ident}_State.hh"
#include "mem/ruby/protocol/Types.hh"
#include "mem/ruby/system/RubySystem.hh"
#define HASH_FUN(state, event) ((int(state)*${ident}_Event_NUM)+int(event))
#define GET_TRANSITION_COMMENT() (${ident}_transitionComment.str())
#define CLEAR_TRANSITION_COMMENT() (${ident}_transitionComment.str(""))
namespace gem5
{
namespace ruby
{
TransitionResult
${ident}_Controller::doTransition(${ident}_Event event,
''')
if self.EntryType != None:
code('''
${{self.EntryType.c_ident}}* m_cache_entry_ptr,
''')
if self.TBEType != None:
code('''
${{self.TBEType.c_ident}}* m_tbe_ptr,
''')
code('''
Addr addr)
{
''')
code.indent()
if self.TBEType != None and self.EntryType != None:
code('${ident}_State state = getState(m_tbe_ptr, m_cache_entry_ptr, addr);')
elif self.TBEType != None:
code('${ident}_State state = getState(m_tbe_ptr, addr);')
elif self.EntryType != None:
code('${ident}_State state = getState(m_cache_entry_ptr, addr);')
else:
code('${ident}_State state = getState(addr);')
code('''
${ident}_State next_state = state;
DPRINTF(RubyGenerated, "%s, Time: %lld, state: %s, event: %s, addr: %#x\\n",
*this, curCycle(), ${ident}_State_to_string(state),
${ident}_Event_to_string(event), addr);
TransitionResult result =
''')
if self.TBEType != None and self.EntryType != None:
code('doTransitionWorker(event, state, next_state, m_tbe_ptr, m_cache_entry_ptr, addr);')
elif self.TBEType != None:
code('doTransitionWorker(event, state, next_state, m_tbe_ptr, addr);')
elif self.EntryType != None:
code('doTransitionWorker(event, state, next_state, m_cache_entry_ptr, addr);')
else:
code('doTransitionWorker(event, state, next_state, addr);')
port_to_buf_map, in_msg_bufs, msg_bufs = self.getBufferMaps(ident)
code('''
if (result == TransitionResult_Valid) {
DPRINTF(RubyGenerated, "next_state: %s\\n",
${ident}_State_to_string(next_state));
countTransition(state, event);
DPRINTFR(ProtocolTrace, "%15d %3s %10s%20s %6s>%-6s %#x %s\\n",
curTick(), m_version, "${ident}",
${ident}_Event_to_string(event),
${ident}_State_to_string(state),
${ident}_State_to_string(next_state),
printAddress(addr), GET_TRANSITION_COMMENT());
CLEAR_TRANSITION_COMMENT();
''')
if self.TBEType != None and self.EntryType != None:
code('setState(m_tbe_ptr, m_cache_entry_ptr, addr, next_state);')
code('setAccessPermission(m_cache_entry_ptr, addr, next_state);')
elif self.TBEType != None:
code('setState(m_tbe_ptr, addr, next_state);')
code('setAccessPermission(addr, next_state);')
elif self.EntryType != None:
code('setState(m_cache_entry_ptr, addr, next_state);')
code('setAccessPermission(m_cache_entry_ptr, addr, next_state);')
else:
code('setState(addr, next_state);')
code('setAccessPermission(addr, next_state);')
code('''
} else if (result == TransitionResult_ResourceStall) {
DPRINTFR(ProtocolTrace, "%15s %3s %10s%20s %6s>%-6s %#x %s\\n",
curTick(), m_version, "${ident}",
${ident}_Event_to_string(event),
${ident}_State_to_string(state),
${ident}_State_to_string(next_state),
printAddress(addr), "Resource Stall");
} else if (result == TransitionResult_ProtocolStall) {
DPRINTF(RubyGenerated, "stalling\\n");
DPRINTFR(ProtocolTrace, "%15s %3s %10s%20s %6s>%-6s %#x %s\\n",
curTick(), m_version, "${ident}",
${ident}_Event_to_string(event),
${ident}_State_to_string(state),
${ident}_State_to_string(next_state),
printAddress(addr), "Protocol Stall");
}
return result;
''')
code.dedent()
code('''
}
TransitionResult
${ident}_Controller::doTransitionWorker(${ident}_Event event,
${ident}_State state,
${ident}_State& next_state,
''')
if self.TBEType != None:
code('''
${{self.TBEType.c_ident}}*& m_tbe_ptr,
''')
if self.EntryType != None:
code('''
${{self.EntryType.c_ident}}*& m_cache_entry_ptr,
''')
code('''
Addr addr)
{
m_curTransitionEvent = event;
m_curTransitionNextState = next_state;
switch(HASH_FUN(state, event)) {
''')
# This map will allow suppress generating duplicate code
cases = OrderedDict()
for trans in self.transitions:
case_string = "%s_State_%s, %s_Event_%s" % \
(self.ident, trans.state.ident, self.ident, trans.event.ident)
case = self.symtab.codeFormatter()
# Only set next_state if it changes
if trans.state != trans.nextState:
if trans.nextState.isWildcard():
# When * is encountered as an end state of a transition,
# the next state is determined by calling the
# machine-specific getNextState function. The next state
# is determined before any actions of the transition
# execute, and therefore the next state calculation cannot
# depend on any of the transitionactions.
case('next_state = getNextState(addr); '
'm_curTransitionNextState = next_state;')
else:
ns_ident = trans.nextState.ident
case('next_state = ${ident}_State_${ns_ident}; '
'm_curTransitionNextState = next_state;')
actions = trans.actions
request_types = trans.request_types
# Check for resources
case_sorter = []
res = trans.resources
for key,val in res.items():
val = '''
if (!%s.areNSlotsAvailable(%s, clockEdge()))
return TransitionResult_ResourceStall;
''' % (key.code, val)
case_sorter.append(val)
# Check all of the request_types for resource constraints
for request_type in request_types:
val = '''
if (!checkResourceAvailable(%s_RequestType_%s, addr)) {
return TransitionResult_ResourceStall;
}
''' % (self.ident, request_type.ident)
case_sorter.append(val)
# Emit the code sequences in a sorted order. This makes the
# output deterministic (without this the output order can vary
# since Map's keys() on a vector of pointers is not deterministic
for c in sorted(case_sorter):
case("$c")
# Record access types for this transition
for request_type in request_types:
case('recordRequestType(${ident}_RequestType_${{request_type.ident}}, addr);')
# Figure out if we stall
stall = False
for action in actions:
if action.ident == "z_stall":
stall = True
break
if stall:
case('return TransitionResult_ProtocolStall;')
else:
if self.TBEType != None and self.EntryType != None:
for action in actions:
case('${{action.ident}}(m_tbe_ptr, m_cache_entry_ptr, addr);')
elif self.TBEType != None:
for action in actions:
case('${{action.ident}}(m_tbe_ptr, addr);')
elif self.EntryType != None:
for action in actions:
case('${{action.ident}}(m_cache_entry_ptr, addr);')
else:
for action in actions:
case('${{action.ident}}(addr);')
case('return TransitionResult_Valid;')
case = str(case)
# Look to see if this transition code is unique.
if case not in cases:
cases[case] = []
cases[case].append(case_string)
# Walk through all of the unique code blocks and spit out the
# corresponding case statement elements
for case,transitions in cases.items():
# Iterative over all the multiple transitions that share
# the same code
for trans in transitions:
code(' case HASH_FUN($trans):')
code(' $case\n')
code('''
default:
panic("Invalid transition\\n"
"%s time: %d addr: %#x event: %s state: %s\\n",
name(), curCycle(), addr, event, state);
}
return TransitionResult_Valid;
}
} // namespace ruby
} // namespace gem5
''')
code.write(path, "%s_Transitions.cc" % self.ident)
# **************************
# ******* HTML Files *******
# **************************
def frameRef(self, click_href, click_target, over_href, over_num, text):
code = self.symtab.codeFormatter(fix_newlines=False)
code("""<A href=\"$click_href\" target=\"$click_target\" onmouseover=\"
if (parent.frames[$over_num].location != parent.location + '$over_href') {
parent.frames[$over_num].location='$over_href'
}\">
${{html.formatShorthand(text)}}
</A>""")
return str(code)
def writeHTMLFiles(self, path):
# Create table with no row hilighted
self.printHTMLTransitions(path, None)
# Generate transition tables
for state in self.states.values():
self.printHTMLTransitions(path, state)
# Generate action descriptions
for action in self.actions.values():
name = "%s_action_%s.html" % (self.ident, action.ident)
code = html.createSymbol(action, "Action")
code.write(path, name)
# Generate state descriptions
for state in self.states.values():
name = "%s_State_%s.html" % (self.ident, state.ident)
code = html.createSymbol(state, "State")
code.write(path, name)
# Generate event descriptions
for event in self.events.values():
name = "%s_Event_%s.html" % (self.ident, event.ident)
code = html.createSymbol(event, "Event")
code.write(path, name)
def printHTMLTransitions(self, path, active_state):
code = self.symtab.codeFormatter()
code('''
<HTML>
<BODY link="blue" vlink="blue">
<H1 align="center">${{html.formatShorthand(self.short)}}:
''')
code.indent()
for i,machine in enumerate(self.symtab.getAllType(StateMachine)):
mid = machine.ident
if i != 0:
extra = " - "
else:
extra = ""
if machine == self:
code('$extra$mid')
else:
code('$extra<A target="Table" href="${mid}_table.html">$mid</A>')
code.dedent()
code("""
</H1>
<TABLE border=1>
<TR>
<TH> </TH>
""")
for event in self.events.values():
href = "%s_Event_%s.html" % (self.ident, event.ident)
ref = self.frameRef(href, "Status", href, "1", event.short)
code('<TH bgcolor=white>$ref</TH>')
code('</TR>')
# -- Body of table
for state in self.states.values():
# -- Each row
if state == active_state:
color = "yellow"
else:
color = "white"
click = "%s_table_%s.html" % (self.ident, state.ident)
over = "%s_State_%s.html" % (self.ident, state.ident)
text = html.formatShorthand(state.short)
ref = self.frameRef(click, "Table", over, "1", state.short)
code('''
<TR>
<TH bgcolor=$color>$ref</TH>
''')
# -- One column for each event
for event in self.events.values():
trans = self.table.get((state,event), None)
if trans is None:
# This is the no transition case
if state == active_state:
color = "#C0C000"
else:
color = "lightgrey"
code('<TD bgcolor=$color>&nbsp;</TD>')
continue
next = trans.nextState
stall_action = False
# -- Get the actions
for action in trans.actions:
if action.ident == "z_stall" or \
action.ident == "zz_recycleMandatoryQueue":
stall_action = True
# -- Print out "actions/next-state"
if stall_action:
if state == active_state:
color = "#C0C000"
else:
color = "lightgrey"
elif active_state and next.ident == active_state.ident:
color = "aqua"
elif state == active_state:
color = "yellow"
else:
color = "white"
code('<TD bgcolor=$color>')
for action in trans.actions:
href = "%s_action_%s.html" % (self.ident, action.ident)
ref = self.frameRef(href, "Status", href, "1",
action.short)
code(' $ref')
if next != state:
if trans.actions:
code('/')
click = "%s_table_%s.html" % (self.ident, next.ident)
over = "%s_State_%s.html" % (self.ident, next.ident)
ref = self.frameRef(click, "Table", over, "1", next.short)
code("$ref")
code("</TD>")
# -- Each row
if state == active_state:
color = "yellow"
else:
color = "white"
click = "%s_table_%s.html" % (self.ident, state.ident)
over = "%s_State_%s.html" % (self.ident, state.ident)
ref = self.frameRef(click, "Table", over, "1", state.short)
code('''
<TH bgcolor=$color>$ref</TH>
</TR>
''')
code('''
<!- Column footer->
<TR>
<TH> </TH>
''')
for event in self.events.values():
href = "%s_Event_%s.html" % (self.ident, event.ident)
ref = self.frameRef(href, "Status", href, "1", event.short)
code('<TH bgcolor=white>$ref</TH>')
code('''
</TR>
</TABLE>
</BODY></HTML>
''')
if active_state:
name = "%s_table_%s.html" % (self.ident, active_state.ident)
else:
name = "%s_table.html" % self.ident
code.write(path, name)
__all__ = [ "StateMachine" ]