# 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 #include #include #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 eventVec; std::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 #include #include #include #include #include """ ) 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()); 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::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::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 #include #include #include #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 #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( """ ${{html.formatShorthand(text)}} """ ) 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.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$mid' ) code.dedent() code( """

""" ) for event in self.events.values(): href = f"{self.ident}_Event_{event.ident}.html" ref = self.frameRef(href, "Status", href, "1", event.short) code("") code("") # -- 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( """ """ ) # -- 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("") 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("") # -- 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( """ """ ) code( """ """ ) for event in self.events.values(): href = f"{self.ident}_Event_{event.ident}.html" ref = self.frameRef(href, "Status", href, "1", event.short) code("") code( """
$ref
$ref ") 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("$ref
$ref
""" ) 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"]