diff --git a/generator/transform/GeneratePML.py b/generator/transform/GeneratePML.py index ecb5dfdad34fff12457025778567d3dd0964dd1f..1661d71ae968236af60da8609c245eb4102288a7 100644 --- a/generator/transform/GeneratePML.py +++ b/generator/transform/GeneratePML.py @@ -1,12 +1,14 @@ from generator.analysis.Analysis import Analysis, PassInformation from generator.analysis import SavedStateTransition, Function from generator.analysis.AtomicBasicBlock import E, S +from generator.analysis.common import * from collections import defaultdict import os import logging import yaml +import networkx -class GeneratePML(Analysis): +class GeneratePML(Analysis, GraphObject): """ FIXME """ @@ -16,54 +18,92 @@ class GeneratePML(Analysis): used_cfg_edges = {E.function_level, E.timing_edge_0, E.timing_edge_1} ) - def get_circuit_states(self, start, end): - start, end = set(start), set(end) - # 0. Find all CFG successors - stop = set() - for abb in end: - stop.update(abb.get_outgoing_nodes(E.function_level)) - # 1. Find all states that will execute one ABB from the end set + def __init__(self): + Analysis.__init__(self) + GraphObject.__init__(self, "GeneratePML", root = True) + + def graph_subobjects(self): + subobjects = [] + for circuit in self.circuits: + if len(circuit['all_states']) > 500: + continue + + mapping = {} + for state in circuit['all_states'].values(): + color = 'green' + if id(state) in circuit['end_states']: + color = 'red' + if id(state) in circuit['start_states']: + color='blue' + label="N%d:%s/%s" %(circuit['state_to_idx'][id(state)], + state.current_subtask, + state.current_abb) + node = GraphObjectContainer(label, color=color) + mapping[id(state)] = node + subobjects.append(node) + + for a in circuit['all_states'].values(): + for b in a.get_outgoing_nodes(SavedStateTransition): + if id(b) in circuit['all_states']: + mapping[id(a)].edges.append( + Edge(mapping[id(a)], mapping[id(b)])) + + return subobjects + + + def get_circuit_states(self, start_abbs, end_abbs): + # 1. Find all starting states sse = self.system_graph.get_pass("sse") - start_states, end_states, stop_states = [], [], [] + start_states, end_states = {}, {} for state in sse.states: - if state.current_abb in start: - start_states.append(state) - if state.current_abb in end: - end_states.append(state) - start_state_ids = {id(x) for x in start_states} - end_state_ids = {id(x) for x in end_states} - # stop_state_ids = {id(x) for x in stop_states} + if state.current_abb in start_abbs: + start_states[id(state)] = state # 2. DFS starting from the first element in the graph. # 2.1 All end states are definitly part of the graph good_states = { } + def add_to_good_states(path, curr): + for x in path + [curr]: + good_states[id(x)] = x def dfs(path, curr): # Uncomment for debugging purposes # print("V", [(x.current_subtask, x.current_abb) for x in path + [curr]]) - if id(curr) in end_state_ids or id(curr) in good_states or\ - id(curr) in [id(x) for x in path]: + state_loop = id(curr) in [id(x) for x in path] + hit_on_good_state = id(curr) in good_states + is_end_state = curr.current_abb in end_abbs + + if state_loop or hit_on_good_state or is_end_state: # print("A") # Add the path to the good states - for x in path + [curr]: - good_states[id(x)] = x + add_to_good_states(path, curr) + + assert not is_end_state or (end_abbs[curr.current_abb] & 1) == 0, \ + "TIMING_POINT_STOP_BEFORE should not be directly visited" + + if is_end_state: + end_states[id(curr)] = curr # Stop DFS, if we were here already if id(curr) in visited: return - visited.add(id(curr)) path = path + [curr] for succ in curr.get_outgoing_nodes(SavedStateTransition): - # Do not proceed locally on state graph within subtask - if id(curr) in end_state_ids: - if succ.current_subtask == curr.current_subtask: + # Edges that origin from an end state to the same + # subtask are not taken. + if is_end_state and succ.current_subtask == curr.current_subtask: continue + if succ.current_abb in end_abbs and (end_abbs[succ.current_abb] & 1) == 1: + # TIMING_POINT_STOP_BEFORE + end_states[id(curr)] = curr + add_to_good_states(path, curr) + continue dfs(path, succ) visited = set() - for state in start_states: + for state in start_states.values(): dfs([], state) # for state in good_states.values(): @@ -73,11 +113,11 @@ class GeneratePML(Analysis): logging.warning("Idle state part of circuit... unanalyzable") # Every State can only occur only once in the result - return good_states.values() + return start_states, good_states, end_states - def find_irq_regions(self, circuit): + def find_irq_regions(self, all_states): """ Find all IRQ regions in a circuit. A IRQ region has one ISR kickoff, and N ISR exits""" - for state in circuit['states']: + for state in all_states.values(): if state.current_subtask and state.current_subtask.conf.is_isr \ and state.current_abb.isA(S.kickoff): assert len(state.get_incoming_nodes(SavedStateTransition)) == 1,\ @@ -86,7 +126,7 @@ class GeneratePML(Analysis): stack = [state] visited = set() entry = state - exits = set() + exits = {} states = [] while stack: p = stack.pop() @@ -97,18 +137,22 @@ class GeneratePML(Analysis): if p.current_abb.isA(S.iret): assert len(p.get_outgoing_nodes(SavedStateTransition)) == 1,\ "Unexpected IRET state" - exits.add(p) + exits[id(p)] = p else: - stack.extend(p.get_outgoing_nodes(SavedStateTransition)) - yield {'entry': entry, 'states': states, 'exits': exits} - - def add_cfg_edges(self, circuit): + for n in p.get_outgoing_nodes(SavedStateTransition): + if id(n) in all_states: + stack.append(n) + else: + exits[id(p)] = p + yield {'entry': entry, 'states': states, 'exits': exits.values()} + + def add_cfg_edges(self, circuit_number, all_states): """Draw Edges (only for visualization) between the connected ABBs""" edges = set([]) - edge_type = [E.timing_edge_0, E.timing_edge_1][circuit["circuit"] % 2] - for a in circuit["states"]: + edge_type = [E.timing_edge_0, E.timing_edge_1][circuit_number % 2] + for a in all_states.values(): for b in a.get_outgoing_nodes(SavedStateTransition): - if id(b) not in circuit["state-ids"]: + if id(b) not in all_states: continue if (a.current_abb, b.current_abb) in edges: continue @@ -121,9 +165,11 @@ class GeneratePML(Analysis): self.flow_frequency_variable_counter += 1 return "flow_var_" + hint + "_" + str(tmp) - def add_circuit_to_pml(self, pml, circuit): + def add_circuit_to_pml(self, pml, circuit_number, + start_states, all_states, end_states, + loops): """Adds the global-cfg field to the PML""" - gcfg = {'name': 'timing-%d' % circuit['circuit'], + gcfg = {'name': 'timing-%d' % circuit_number, 'level': 'bitcode', 'entry-nodes': [], 'exit-nodes': [], @@ -136,23 +182,34 @@ class GeneratePML(Analysis): gcfg['blocks'].append(data) return i, data def delete_block(idx): + assert False gcfg['blocks'][idx] = None # First we generate the artificial blocks isr_entry_abb_idx, _ = add_block({'function': 'irq_entry', 'name':"isr_entry_block"}) - # Assign an idx to every state and abb + # Assign an idx to every state and abb. States are monotonly + # increasing numbers that refence objects in the PML abb_to_idx = {} state_to_idx = {} - def idx(state): - if id(state) in state_to_idx: - return state_to_idx[id(state)] - return abb_to_idx[state] + def idx(obj): + if id(obj) in state_to_idx: + return state_to_idx[id(obj)] + return abb_to_idx[obj] timer_function_name = '_ZN2os7Counter4tickEv' - for state in circuit['states']: + # Some states are part of a toplevel loop + state_to_loops = defaultdict(lambda: list()) + for loop, states in loops.items(): + for state in states: + state_to_loops[id(state)].append(loop) + + for state in all_states.values(): + # We first assign an id to the state state_to_idx[id(state)] = len(state_to_idx) + + # We have to add an ABB object to the PML, for every ABB we newly ancounter abb = state.current_abb if abb not in abb_to_idx: # ISR entry and Exit Nodes are filled separatly @@ -184,25 +241,30 @@ class GeneratePML(Analysis): record['exit-block'] = 'idle_loop_again' - # Some states are entry states, some states are know to be - # exit nodes - if abb in circuit['start']: - gcfg['entry-nodes'].append(idx(state)) - if abb in circuit['end']: - gcfg['exit-nodes'].append(idx(state)) + + # After having an index for every state, we can mark states as + # entry or exit nodes. + for state in start_states.values(): + gcfg['entry-nodes'].append(idx(state)) + for state in end_states.values(): + gcfg['exit-nodes'].append(idx(state)) # Add all state records to the gcfg - for state in circuit['states']: + for state in all_states.values(): successors = [x for x in state.get_outgoing_nodes(SavedStateTransition) - if id(x) in circuit['state-ids']] + if id(x) in all_states] local_successors = [idx(x) for x in successors if x.current_subtask == state.current_subtask] global_successors = [idx(x) for x in successors if x.current_subtask != state.current_subtask] - successors = [idx(x) for x in successors if id(x) in circuit['state-ids']] + successors = [idx(x) for x in successors if id(x) in all_states] data = {'index': idx(state), 'local-successors': local_successors, 'global-successors': global_successors} + # Annotate that states are part of a loop + if id(state) in state_to_loops: + data['loops'] = state_to_loops[id(state)] + if state.current_abb in abb_to_idx: data['abb'] = abb_to_idx[state.current_abb] data['abb-name'] = str(state.current_abb) @@ -215,17 +277,12 @@ class GeneratePML(Analysis): gcfg['nodes'].append(data) irq_entry_variables = defaultdict(list) - #print (circuit['states']) # Transform IRQ Regions ast_requests = set() - for region in self.find_irq_regions(circuit): + for region in self.find_irq_regions(all_states): isr_entry = region['entry'] isr_entry_data = gcfg['nodes'][idx(isr_entry)] - # Sometimes an IRQ entry can be - if region['entry'].current_abb in circuit['start']: - gcfg['entry-nodes'].append(idx(isr_entry)) - # Give the isr_entry block a flow variable. This flow # variable, will capture how often an interrupt is @@ -240,7 +297,6 @@ class GeneratePML(Analysis): isr_entry_data['abb'] = isr_entry_abb_idx isr_entry_data['abb-name'] = 'isr_entry' - for isr_exit in region['exits']: if id(isr_exit) not in state_to_idx: continue @@ -253,8 +309,7 @@ class GeneratePML(Analysis): # flow. This is necessary, since they are embedded into # the isr_entry state for state in region['states']: - if id(state) not in state_to_idx: - continue + assert id(state) in state_to_idx state_data = gcfg['nodes'][idx(state)] state_data['abb-name'] = str(state.current_abb) if state != isr_entry: @@ -331,9 +386,28 @@ class GeneratePML(Analysis): 'lhs': lhs , }) - logging.info(" %s: %d states", gcfg['name'], len(gcfg['nodes'])) + logging.info(" %s: %d states, %d loops", gcfg['name'], + len(gcfg['nodes']), + len(loops)) + return state_to_idx + def find_loops(self, all_states): + # Create Directed Graph + G = networkx.DiGraph() + + # Add a list of nodes: + G.add_nodes_from(all_states.keys()) + + for a in all_states.values(): + for b in a.get_outgoing_nodes(SavedStateTransition): + if id(b) in all_states: + G.add_edge(id(a), id(b)) + loops = {} + for loop in networkx.simple_cycles(G): + loop_id = len(loops) + loops[loop_id] = [all_states[state_id] for state_id in loop] + return loops def do(self): self.flow_frequency_variable_counter = 0 @@ -348,37 +422,54 @@ class GeneratePML(Analysis): # We search for all circuits, the user has annotated with # function calls in his application - circuits = defaultdict(lambda : {'start': [], 'end': []}) + circuits = defaultdict(lambda : {'start': {}, 'end': {}}) for abb in self.system_graph.abbs: - for (bb, func, circuit) in abb.call_sites: + for (bb, func, args) in abb.call_sites: + if func in ('timing_start', 'timing_end'): + circuit = args[0] & 0xff + options = args[0] >> 8 if func == "timing_start": if abb.subtask.conf.is_isr: - logging.info(" timing-%d starts with IRQ activation", circuit[0]) + logging.info(" timing-%d starts with IRQ activation", circuit) before = abb.definite_before(E.function_level) assert before.isA(S.kickoff), "Can start analysis only at beginning of ISR" - circuits[circuit[0]]["start"].append(before) + circuits[circuit]["start"][before] = options else: - circuits[circuit[0]]["start"].append(abb) + circuits[circuit]["start"][abb] = options if func == "timing_end": - circuits[circuit[0]]["end"].append(abb) + circuits[circuit]["end"][abb] = options # Finds all states in between - for circuit, data in circuits.items(): - states = self.get_circuit_states(data["start"], data["end"]) - - data['states'] = states - data['circuit'] = circuit # Copy ID into dict - data['state-ids'] = {id(x) for x in data['states']} - self.add_cfg_edges(data) - - - self.circuits = circuits.values() - pml = {'format': 'pml-0.1', 'triple': 'patmos-unknown-unknown-elf', 'global-cfgs': [] } - for circuit in self.circuits: - self.add_circuit_to_pml(pml, circuit) + + self.circuits = [] + for circuit_number, data in circuits.items(): + start_states, all_states, end_states = self.get_circuit_states(data["start"], data["end"]) + + #print([x.current_abb for x in start_states.values()]) + #print([x.current_abb for x in all_states.values()]) + #print([x.current_abb for x in end_states.values()]) + assert all([id_ in all_states for id_ in start_states]), \ + "All start states must be part of the all_state set" + assert all([id_ in all_states for id_ in end_states]), \ + "All end states must be part of the all_state set" + + # Copy circuit into graph (combined ABB->ABB edges) + self.add_cfg_edges(circuit_number, all_states) + + # Add Circuit to PML + loops = self.find_loops(all_states) + state_to_idx = self.add_circuit_to_pml(pml, circuit_number, + start_states, all_states, end_states, + loops) + + self.circuits.append({"index": circuit_number, + 'state_to_idx': state_to_idx, + 'start_states': start_states, + 'all_states': all_states, + 'end_states': end_states}) fn = os.path.join(os.path.dirname(self.system_graph.basefilename),