diff --git a/osaca/api/kerncraft_interface.py b/osaca/api/kerncraft_interface.py index e065281..d21a32d 100755 --- a/osaca/api/kerncraft_interface.py +++ b/osaca/api/kerncraft_interface.py @@ -49,4 +49,9 @@ class KerncraftAPI(object): def get_latency(self, kernel): kernel_graph = KernelDG(kernel, self.parser, self.machine_model) - return sum([x if x['latency'] is not None else 0 for x in kernel_graph]) + return sum( + [ + x['latency'] if x['latency'] is not None else 0 + for x in kernel_graph.get_critical_path() + ] + ) diff --git a/osaca/data/vulcan.yml b/osaca/data/vulcan.yml index 783a993..127be41 100644 --- a/osaca/data/vulcan.yml +++ b/osaca/data/vulcan.yml @@ -62,6 +62,12 @@ instruction_forms: throughput: 0.33333333 latency: 1.0 # 0 0DV 1 1DV 2 3 4 5 port_pressure: [0.33333333, 0.0, 0.33333333, 0.0, 0.33333333, 0.0, 0.0, 0.0] + - name: "b.ne" + operands: + - class: 'identifier' + throughput: 0.0 + latency: 0.0 # 0 0DV 1 1DV 2 3 4 5 + port_pressure: [0, 0, 0, 0, 0, 0, 0, 0] - name: "bne" operands: - class: 'identifier' diff --git a/osaca/semantics/semanticsAppender.py b/osaca/semantics/semanticsAppender.py index 2428dff..a0c44d1 100755 --- a/osaca/semantics/semanticsAppender.py +++ b/osaca/semantics/semanticsAppender.py @@ -13,8 +13,8 @@ class INSTR_FLAGS: Flags used for unknown or special instructions """ - TP_UNKWN = 'tp_unkown' - LT_UNKWN = 'lt_unkown' + TP_UNKWN = 'tp_unknown' + LT_UNKWN = 'lt_unknown' NOT_BOUND = 'not_bound' HIDDEN_LD = 'hidden_load' HAS_LD = 'performs_load' @@ -76,7 +76,9 @@ class SemanticsAppender(object): if INSTR_FLAGS.HIDDEN_LD not in load_instr['flags'] ] ) - load = [instr for instr in kernel if instr['line_number'] == min_distance_load[1]][0] + load = [instr for instr in kernel if instr['line_number'] == min_distance_load[1]][ + 0 + ] # Hide load load['flags'] += [INSTR_FLAGS.HIDDEN_LD] load['port_pressure'] = self._nullify_data_ports(load['port_pressure']) @@ -86,43 +88,49 @@ class SemanticsAppender(object): def assign_tp_lt(self, instruction_form): flags = [] port_number = len(self._machine_model['ports']) - instruction_data = self._machine_model.get_instruction( - instruction_form['instruction'], instruction_form['operands'] - ) - if instruction_data: - # instruction form in DB - throughput = instruction_data['throughput'] - port_pressure = instruction_data['port_pressure'] - try: - assert isinstance(port_pressure, list) - assert len(port_pressure) == port_number - instruction_form['port_pressure'] = port_pressure - if sum(port_pressure) == 0 and throughput is not None: - # port pressure on all ports 0 --> not bound to a port - flags.append(INSTR_FLAGS.NOT_BOUND) - except AssertionError: - warnings.warn( - 'Port pressure could not be imported correctly from database. ' - + 'Please check entry for:\n {}'.format(instruction_form) - ) - instruction_form['port_pressure'] = [0.0 for i in range(port_number)] - flags.append(INSTR_FLAGS.TP_UNKWN) - if throughput is None: - # assume 0 cy and mark as unknown - throughput = 0.0 - flags.append(INSTR_FLAGS.TP_UNKWN) - latency = instruction_data['latency'] - if latency is None: - # assume 0 cy and mark as unknown - latency = 0.0 - flags.append(INSTR_FLAGS.LT_UNKWN) - else: - # instruction could not be found in DB - # --> mark as unknown and assume 0 cy for latency/throughput + if instruction_form['instruction'] is None: + # No instruction (label, comment, ...) --> ignore throughput = 0.0 latency = 0.0 instruction_form['port_pressure'] = [0.0 for i in range(port_number)] - flags += [INSTR_FLAGS.TP_UNKWN, INSTR_FLAGS.LT_UNKWN] + else: + instruction_data = self._machine_model.get_instruction( + instruction_form['instruction'], instruction_form['operands'] + ) + if instruction_data: + # instruction form in DB + throughput = instruction_data['throughput'] + port_pressure = instruction_data['port_pressure'] + try: + assert isinstance(port_pressure, list) + assert len(port_pressure) == port_number + instruction_form['port_pressure'] = port_pressure + if sum(port_pressure) == 0 and throughput is not None: + # port pressure on all ports 0 --> not bound to a port + flags.append(INSTR_FLAGS.NOT_BOUND) + except AssertionError: + warnings.warn( + 'Port pressure could not be imported correctly from database. ' + + 'Please check entry for:\n {}'.format(instruction_form) + ) + instruction_form['port_pressure'] = [0.0 for i in range(port_number)] + flags.append(INSTR_FLAGS.TP_UNKWN) + if throughput is None: + # assume 0 cy and mark as unknown + throughput = 0.0 + flags.append(INSTR_FLAGS.TP_UNKWN) + latency = instruction_data['latency'] + if latency is None: + # assume 0 cy and mark as unknown + latency = 0.0 + flags.append(INSTR_FLAGS.LT_UNKWN) + else: + # instruction could not be found in DB + # --> mark as unknown and assume 0 cy for latency/throughput + throughput = 0.0 + latency = 0.0 + instruction_form['port_pressure'] = [0.0 for i in range(port_number)] + flags += [INSTR_FLAGS.TP_UNKWN, INSTR_FLAGS.LT_UNKWN] # flatten flag list flags = list(set(flags)) if 'flags' not in instruction_form: diff --git a/tests/test_kerncraftAPI.py b/tests/test_kerncraftAPI.py new file mode 100755 index 0000000..ef87844 --- /dev/null +++ b/tests/test_kerncraftAPI.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +""" +Unit tests for OSACA Kerncraft API +""" + +import os +import unittest + +from collections import OrderedDict + +from osaca.api import KerncraftAPI +from osaca.parser import ParserAArch64v81, ParserX86ATT + + +class TestKerncraftAPI(unittest.TestCase): + @classmethod + def setUpClass(self): + # set up parser and kernels + self.parser_x86 = ParserX86ATT() + self.parser_AArch64 = ParserAArch64v81() + with open(self._find_file('triad-x86-iaca.s')) as f: + self.code_x86 = f.read() + with open(self._find_file('triad-arm-iaca.s')) as f: + self.code_AArch64 = f.read() + + ########### + # Tests + ########### + + def test_kerncraft_API_x86(self): + kapi = KerncraftAPI('csx') + kernel = kapi.analyze_code(self.code_x86) + + kapi.create_output(kernel) + self.assertEqual(kapi.get_unmatched_instruction_ratio(kernel), 0.0) + port_occupation = OrderedDict( + [ + ('0', 1.25), + ('0DV', 0.0), + ('1', 1.25), + ('2', 2.0), + ('2D', 1.5), + ('3', 2.0), + ('3D', 1.5), + ('4', 1.0), + ('5', 0.75), + ('6', 0.75), + ('7', 0.0), + ] + ) + self.assertEqual(kapi.get_port_occupation_cycles(kernel), port_occupation) + self.assertEqual(kapi.get_total_throughput(kernel), 2.0) + self.assertEqual(kapi.get_latency(kernel), 10.0) + + def test_kerncraft_API_AArch64(self): + kapi = KerncraftAPI('vulcan') + kernel = kapi.analyze_code(self.code_AArch64) + + kapi.create_output(kernel) + self.assertEqual(kapi.get_unmatched_instruction_ratio(kernel), 0.0) + port_occupation = OrderedDict( + [ + ('0', 34.0), + ('0DV', 0.0), + ('1', 34.0), + ('1DV', 0.0), + ('2', 2.0), + ('3', 64.0), + ('4', 64.0), + ('5', 32.0), + ] + ) + self.assertEqual(kapi.get_port_occupation_cycles(kernel), port_occupation) + self.assertEqual(kapi.get_total_throughput(kernel), 64.0) + # TODO add missing latency values + # self.assertEqual(kapi.get_latency(kernel), 20.0) + + ################## + # Helper functions + ################## + + @staticmethod + def _find_file(name): + testdir = os.path.dirname(__file__) + name = os.path.join(testdir, 'test_files', name) + assert os.path.exists(name) + return name + + +if __name__ == '__main__': + suite = unittest.TestLoader().loadTestsFromTestCase(TestKerncraftAPI) + unittest.TextTestRunner(verbosity=2).run(suite)