Currently the RubySystem pointer is set when set_tbe is performed, which effectively clears the NetDest objects from the TBE (if any). This is fine if the TBE has been just allocated before set_tbe is called (no NetDest info in the TBE). However, the CHI protocol has an action (RestoreFromHazard) that performs a set_tbe over a TBE that had already been set, i.e., it already has valid NetDest data. This patch sets the RubySystem pointer when the TBE is allocated, which is more natural and follows the style already adopted in the PerfectCacheMemory class (#1864). Co-authored-by: Adrià Armejach <adria.armejach@bsc.es>
2146 lines
64 KiB
Python
2146 lines
64 KiB
Python
# Copyright (c) 2019-2021,2023 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.
|
|
|
|
import re
|
|
from collections import OrderedDict
|
|
|
|
import slicc.generate.html as html
|
|
from slicc.symbols.Symbol import Symbol
|
|
from slicc.symbols.Var import Var
|
|
|
|
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",
|
|
"prefetch::Base": "BasePrefetcher",
|
|
"Cycles": "Cycles",
|
|
"Addr": "Addr",
|
|
}
|
|
|
|
|
|
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,
|
|
f"(*m_{param.ident}_ptr)",
|
|
{},
|
|
self,
|
|
)
|
|
else:
|
|
var = Var(
|
|
symtab,
|
|
param.ident,
|
|
location,
|
|
param.type_ast.type,
|
|
f"m_{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.transitions_per_ev = {}
|
|
self.in_ports = []
|
|
self.functions = []
|
|
self.event_stats_in_trans = []
|
|
self.event_stats_out_trans = []
|
|
|
|
# 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
|
|
# Python's sets are not sorted so we have to be careful when using
|
|
# this to generate deterministic output.
|
|
self.debug_flags = set()
|
|
self.debug_flags.add("RubyGenerated")
|
|
self.debug_flags.add("RubySlicc")
|
|
|
|
def __repr__(self):
|
|
return f"[StateMachine: {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
|
|
if "in_trans" in event.pairs:
|
|
self.event_stats_in_trans.append(event)
|
|
if "out_trans" in event.pairs:
|
|
self.event_stats_out_trans.append(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(f"Duplicate action definition: {action.ident}")
|
|
action.error(f"Duplicate action definition: {action.ident}")
|
|
if action.short == other.short:
|
|
other.warning(f"Duplicate action shorthand: {other.ident}")
|
|
other.warning(f" shorthand = {other.short}")
|
|
action.warning(f"Duplicate action shorthand: {action.ident}")
|
|
action.error(f" shorthand = {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)
|
|
if trans.event not in self.transitions_per_ev:
|
|
self.transitions_per_ev[trans.event] = []
|
|
self.transitions_per_ev[trans.event].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 = f"{type.c_ident}"
|
|
|
|
if type_ident == f"{self.ident}_TBE":
|
|
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(f"Duplicate transition: {table[index]}")
|
|
trans.error(f"Duplicate transition: {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 = f"Unused action: {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 = f"m_{port.pairs['buffer_expr'].name}_ptr"
|
|
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
|
|
|
|
protocol = self.symtab.slicc.protocol
|
|
py_ident = f"{protocol}_{ident}_Controller"
|
|
c_ident = f"{self.ident}_Controller"
|
|
gen_filename = f"{protocol}/{py_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/${protocol}/${c_ident}.hh'
|
|
cxx_class = 'gem5::ruby::$protocol::$c_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()
|
|
|
|
# Needed for backwards compatibility. If you have exactly 1 protocol
|
|
# (i.e., buildEnv["PROTOCOL"] is not MULTIPLE), then for the one
|
|
# type of machine that matches this (sole) protocol, you can create an
|
|
# alias to the new name. This is only needed if using script that
|
|
# reference Ruby.py. When that is deprecated, this code can be removed
|
|
code(
|
|
"""
|
|
|
|
from m5.defines import buildEnv
|
|
from m5.util import warn
|
|
|
|
if buildEnv["PROTOCOL"] == "${protocol}":
|
|
class ${c_ident}(${py_ident}):
|
|
def __init__(self, *args, **kwargs):
|
|
warn(
|
|
"${c_ident} is deprecated. Use %s_${c_ident} instead",
|
|
buildEnv['PROTOCOL']
|
|
)
|
|
super().__init__(*args, **kwargs)
|
|
"""
|
|
)
|
|
|
|
code.write(path, f"{gen_filename}.py")
|
|
|
|
def printControllerHH(self, path):
|
|
"""Output the method declarations for the class declaration"""
|
|
code = self.symtab.codeFormatter()
|
|
ident = self.ident
|
|
c_ident = f"{self.ident}_Controller"
|
|
|
|
protocol = self.symtab.slicc.protocol
|
|
header_string = protocol + "_" + self.ident
|
|
gen_filename = f"{protocol}/{c_ident}"
|
|
py_ident = f"{protocol}_{ident}_Controller"
|
|
|
|
code(
|
|
"""
|
|
// Created by slicc definition of Module "${{self.short}}"
|
|
|
|
#ifndef __${header_string}_CONTROLLER_HH__
|
|
#define __${header_string}_CONTROLLER_HH__
|
|
|
|
#include <iostream>
|
|
#include <sstream>
|
|
#include <string>
|
|
|
|
#include "mem/ruby/common/Consumer.hh"
|
|
#include "mem/ruby/protocol/TransitionResult.hh"
|
|
#include "mem/ruby/protocol/${protocol}/Types.hh"
|
|
#include "mem/ruby/slicc_interface/AbstractController.hh"
|
|
#include "params/$py_ident.hh"
|
|
|
|
"""
|
|
)
|
|
|
|
seen_types = set()
|
|
for var in self.objects:
|
|
if var.type.ident not in seen_types and not var.type.isPrimitive:
|
|
if var.type.shared or var.type.isExternal:
|
|
code(
|
|
"""
|
|
#include "mem/ruby/protocol/${{var.type.c_ident}}.hh"
|
|
"""
|
|
)
|
|
else:
|
|
code(
|
|
"""
|
|
#include "mem/ruby/protocol/${{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
|
|
{
|
|
|
|
namespace ${protocol}
|
|
{
|
|
|
|
extern std::stringstream ${ident}_transitionComment;
|
|
|
|
class $c_ident : public AbstractController
|
|
{
|
|
public:
|
|
typedef ${py_ident}Params Params;
|
|
$c_ident(const Params &p);
|
|
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];
|
|
|
|
std::vector<statistics::Vector *> eventVec;
|
|
std::vector<std::vector<statistics::Vector *> > transVec;
|
|
|
|
// 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 ${protocol}
|
|
} // namespace ruby
|
|
} // namespace gem5
|
|
|
|
#endif // __${header_string}_CONTROLLER_H__
|
|
"""
|
|
)
|
|
|
|
code.write(path, f"{gen_filename}.hh")
|
|
|
|
def printControllerCC(self, path, includes):
|
|
"""Output the actions for performing the actions"""
|
|
|
|
code = self.symtab.codeFormatter()
|
|
ident = self.ident
|
|
c_ident = f"{self.ident}_Controller"
|
|
gen_filename = f"{self.symtab.slicc.protocol}/{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)
|
|
# We have to sort self.debug_flags in order to produce deterministic
|
|
# output and avoid unnecessary rebuilds of the generated files.
|
|
for f in sorted(self.debug_flags):
|
|
code('#include "debug/${{f}}.hh"')
|
|
code(
|
|
"""
|
|
#include "mem/ruby/network/Network.hh"
|
|
#include "mem/ruby/protocol/${gen_filename}_Controller.hh"
|
|
#include "mem/ruby/protocol/${gen_filename}_Event.hh"
|
|
#include "mem/ruby/protocol/${gen_filename}_State.hh"
|
|
#include "mem/ruby/protocol/${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:
|
|
if var.type.shared or var.type.isExternal:
|
|
code(
|
|
"""
|
|
#include "mem/ruby/protocol/${{var.type.c_ident}}.hh"
|
|
"""
|
|
)
|
|
else:
|
|
code(
|
|
"""
|
|
#include "mem/ruby/protocol/${{protocol}}/${{var.type.c_ident}}.hh"
|
|
"""
|
|
)
|
|
seen_types.add(var.type.ident)
|
|
|
|
num_in_ports = len(self.in_ports)
|
|
|
|
code(
|
|
"""
|
|
namespace gem5
|
|
{
|
|
|
|
namespace ruby
|
|
{
|
|
|
|
namespace ${protocol}
|
|
{
|
|
|
|
// 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;
|
|
p.ruby_system->m_num_controllers[MachineType_${ident}]++;
|
|
p.ruby_system->registerAbstractController(this, std::make_unique<${protocol}ProtocolInfo>());
|
|
m_ruby_system = p.ruby_system;
|
|
|
|
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 = m_ruby_system->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 = f"m_{var.ident}_ptr"
|
|
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 = f"m_{var.ident}_ptr"
|
|
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 = f"{vid} = new {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 = f"Type {vtype.ident} default"
|
|
code('*$vid = ${{vtype["default"]}}; // $comment')
|
|
|
|
# For objects that require a pointer to RubySystem,
|
|
# set the value here.
|
|
if vtype.c_ident in (
|
|
"NetDest",
|
|
"PerfectCacheMemory",
|
|
"TBETable",
|
|
):
|
|
code(f"(*{vid}).setRubySystem(m_ruby_system);")
|
|
|
|
for param in self.config_parameters:
|
|
if param.type_ast.type.ident == "CacheMemory":
|
|
assert param.pointer
|
|
code(f"m_{param.ident}_ptr->setRubySystem(m_ruby_system);")
|
|
|
|
# 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 = f"{self.ident}_State_{trans.state.ident}"
|
|
event = f"{self.ident}_Event_{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 = f"m_{param.ident}_ptr"
|
|
|
|
dma_seq_ident = "NULL"
|
|
for param in self.config_parameters:
|
|
if param.ident == "dma_sequencer":
|
|
assert param.pointer
|
|
dma_seq_ident = f"m_{param.ident}_ptr"
|
|
|
|
coal_ident = "NULL"
|
|
for param in self.config_parameters:
|
|
if param.ident == "coalescer":
|
|
assert param.pointer
|
|
coal_ident = f"m_{param.ident}_ptr"
|
|
|
|
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());
|
|
int num_controllers =
|
|
m_ruby_system->m_num_controllers[MachineType_${ident}];
|
|
t->init(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());
|
|
int num_controllers =
|
|
m_ruby_system->m_num_controllers[MachineType_${ident}];
|
|
t->init(num_controllers);
|
|
t->flags(statistics::pdf | statistics::total |
|
|
statistics::oneline | statistics::nozero);
|
|
transVec[state].push_back(t);
|
|
}
|
|
}
|
|
}
|
|
|
|
"""
|
|
)
|
|
# check if Events/States have profiling qualifiers flags for
|
|
# inTransLatHist and outTransLatHist stats.
|
|
ev_ident_list = [
|
|
f"{ident}_Event_{ev.ident}" for ev in self.event_stats_out_trans
|
|
]
|
|
ev_ident_str = "{" + ",".join(ev_ident_list) + "}"
|
|
code(
|
|
"""
|
|
const std::vector<${ident}_Event> out_trans_evs = ${ev_ident_str};
|
|
"""
|
|
)
|
|
ev_ident_list = [
|
|
f"{ident}_Event_{ev.ident}" for ev in self.event_stats_in_trans
|
|
]
|
|
ev_ident_str = "{" + ",".join(ev_ident_list) + "}"
|
|
code(
|
|
"""
|
|
const std::vector<${ident}_Event> in_trans_evs = ${ev_ident_str};
|
|
"""
|
|
)
|
|
kv_ident_list = []
|
|
for ev in self.event_stats_in_trans:
|
|
key_ident = f"{ident}_Event_{ev.ident}"
|
|
val_ident_lst = [
|
|
f"{ident}_State_{trans.state.ident}"
|
|
for trans in self.transitions_per_ev[ev]
|
|
]
|
|
val_ident_str = "{" + ",".join(val_ident_lst) + "}"
|
|
kv_ident_list.append(f"{{{key_ident}, {val_ident_str}}}")
|
|
key_ident_str = "{" + ",".join(kv_ident_list) + "}"
|
|
code(
|
|
"""
|
|
const std::unordered_map<${ident}_Event, std::vector<${ident}_State>>
|
|
in_trans_evs_states = ${key_ident_str};
|
|
"""
|
|
)
|
|
code(
|
|
"""
|
|
|
|
for (const auto event : out_trans_evs) {
|
|
std::string stat_name =
|
|
"outTransLatHist." + ${ident}_Event_to_string(event);
|
|
statistics::Histogram* t =
|
|
new statistics::Histogram(&stats, stat_name.c_str());
|
|
stats.outTransLatHist[event] = 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.outTransRetryCnt[event] = r;
|
|
r->flags(statistics::nozero);
|
|
}
|
|
|
|
for (const auto event : in_trans_evs) {
|
|
std::string stat_name =
|
|
"inTransLatHist." + ${ident}_Event_to_string(event);
|
|
statistics::Histogram* t =
|
|
new statistics::Histogram(&stats, stat_name.c_str());
|
|
stats.inTransLatHist[event] = 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.inTransRetryCnt[event] = r;
|
|
r->flags(statistics::nozero);
|
|
|
|
auto &src_states = stats.inTransStateChanges[event];
|
|
for (const auto initial_state : in_trans_evs_states.at(event)) {
|
|
auto &dst_vector = src_states[initial_state];
|
|
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) + ".total";
|
|
statistics::Scalar* t =
|
|
new statistics::Scalar(&stats, stat_name.c_str());
|
|
t->flags(statistics::nozero);
|
|
dst_vector.push_back(t);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
$c_ident::collateStats()
|
|
{
|
|
int num_controllers =
|
|
m_ruby_system->m_num_controllers[MachineType_${ident}];
|
|
|
|
for (${ident}_Event event = ${ident}_Event_FIRST;
|
|
event < ${ident}_Event_NUM; ++event) {
|
|
for (unsigned int i = 0; i < 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 < 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];
|
|
}
|
|
|
|
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;
|
|
m_cache_entry_ptr->setRubySystem(m_ruby_system);
|
|
}
|
|
|
|
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 = f"m_{var.ident}_ptr"
|
|
code("num_functional_writes += $vid->functionalWrite(pkt);")
|
|
|
|
for var in self.config_parameters:
|
|
vtype = var.type_ast.type
|
|
if vtype.isBuffer:
|
|
vid = f"m_{var.ident}_ptr"
|
|
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 = f"m_{var.ident}_ptr"
|
|
code("if ($vid->functionalRead(pkt)) return true;")
|
|
|
|
for var in self.config_parameters:
|
|
vtype = var.type_ast.type
|
|
if vtype.isBuffer:
|
|
vid = f"m_{var.ident}_ptr"
|
|
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 = f"m_{var.ident}_ptr"
|
|
code("if ($vid->functionalRead(pkt, mask)) read = true;")
|
|
|
|
for var in self.config_parameters:
|
|
vtype = var.type_ast.type
|
|
if vtype.isBuffer:
|
|
vid = f"m_{var.ident}_ptr"
|
|
code("if ($vid->functionalRead(pkt, mask)) read = true;")
|
|
|
|
code(
|
|
"""
|
|
return read;
|
|
}
|
|
|
|
} // namespace ${protocol}
|
|
} // namespace ruby
|
|
} // namespace gem5
|
|
"""
|
|
)
|
|
|
|
code.write(path, f"{gen_filename}_Controller.cc")
|
|
|
|
def printCWakeup(self, path, includes):
|
|
"""Output the wakeup loop for the events"""
|
|
|
|
code = self.symtab.codeFormatter()
|
|
ident = self.ident
|
|
gen_filename = f"{self.symtab.slicc.protocol}/{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"
|
|
|
|
"""
|
|
)
|
|
# We have to sort self.debug_flags in order to produce deterministic
|
|
# output and avoid unnecessary rebuilds of the generated files.
|
|
for f in sorted(self.debug_flags):
|
|
code('#include "debug/${{f}}.hh"')
|
|
code(
|
|
"""
|
|
#include "mem/ruby/protocol/${gen_filename}_Controller.hh"
|
|
#include "mem/ruby/protocol/${gen_filename}_Event.hh"
|
|
#include "mem/ruby/protocol/${gen_filename}_State.hh"
|
|
|
|
"""
|
|
)
|
|
|
|
if outputRequest_types:
|
|
code(
|
|
"""
|
|
#include "mem/ruby/protocol/${protocol}/${ident}_RequestType.hh"
|
|
"""
|
|
)
|
|
|
|
code(
|
|
"""
|
|
#include "mem/ruby/protocol/${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
|
|
{
|
|
|
|
namespace ${protocol}
|
|
{
|
|
|
|
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 ${protocol}
|
|
} // namespace ruby
|
|
} // namespace gem5
|
|
"""
|
|
)
|
|
|
|
code.write(path, f"{gen_filename}_Wakeup.cc")
|
|
|
|
def printCSwitch(self, path):
|
|
"""Output switch statement for transition table"""
|
|
|
|
code = self.symtab.codeFormatter()
|
|
ident = self.ident
|
|
gen_filename = f"{self.symtab.slicc.protocol}/{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/${gen_filename}_Controller.hh"
|
|
#include "mem/ruby/protocol/${gen_filename}_Event.hh"
|
|
#include "mem/ruby/protocol/${gen_filename}_State.hh"
|
|
#include "mem/ruby/protocol/${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
|
|
{
|
|
|
|
namespace ${protocol}
|
|
{
|
|
|
|
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 = "{}_State_{}, {}_Event_{}".format(
|
|
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 = f"""
|
|
if (!{key.code}.areNSlotsAvailable({val}, clockEdge()))
|
|
return TransitionResult_ResourceStall;
|
|
"""
|
|
case_sorter.append(val)
|
|
|
|
# Check all of the request_types for resource constraints
|
|
for request_type in request_types:
|
|
val = """
|
|
if (!checkResourceAvailable({}_RequestType_{}, addr)) {{
|
|
return TransitionResult_ResourceStall;
|
|
}}
|
|
""".format(
|
|
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 ${protocol}
|
|
} // namespace ruby
|
|
} // namespace gem5
|
|
"""
|
|
)
|
|
code.write(path, f"{gen_filename}_Transitions.cc")
|
|
|
|
# **************************
|
|
# ******* 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 = f"{self.ident}_action_{action.ident}.html"
|
|
code = html.createSymbol(action, "Action")
|
|
code.write(path, name)
|
|
|
|
# Generate state descriptions
|
|
for state in self.states.values():
|
|
name = f"{self.ident}_State_{state.ident}.html"
|
|
code = html.createSymbol(state, "State")
|
|
code.write(path, name)
|
|
|
|
# Generate event descriptions
|
|
for event in self.events.values():
|
|
name = f"{self.ident}_Event_{event.ident}.html"
|
|
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 = f"{self.ident}_Event_{event.ident}.html"
|
|
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 = f"{self.ident}_table_{state.ident}.html"
|
|
over = f"{self.ident}_State_{state.ident}.html"
|
|
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> </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 = f"{self.ident}_action_{action.ident}.html"
|
|
ref = self.frameRef(
|
|
href, "Status", href, "1", action.short
|
|
)
|
|
code(" $ref")
|
|
if next != state:
|
|
if trans.actions:
|
|
code("/")
|
|
click = f"{self.ident}_table_{next.ident}.html"
|
|
over = f"{self.ident}_State_{next.ident}.html"
|
|
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 = f"{self.ident}_table_{state.ident}.html"
|
|
over = f"{self.ident}_State_{state.ident}.html"
|
|
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 = f"{self.ident}_Event_{event.ident}.html"
|
|
ref = self.frameRef(href, "Status", href, "1", event.short)
|
|
code("<TH bgcolor=white>$ref</TH>")
|
|
code(
|
|
"""
|
|
</TR>
|
|
</TABLE>
|
|
</BODY></HTML>
|
|
"""
|
|
)
|
|
|
|
if active_state:
|
|
name = f"{self.ident}_table_{active_state.ident}.html"
|
|
else:
|
|
name = f"{self.ident}_table.html"
|
|
code.write(path, name)
|
|
|
|
|
|
__all__ = ["StateMachine"]
|