added API for external tools

This commit is contained in:
Julian Hammer
2019-01-10 13:36:46 +01:00
parent 2c1dd15b36
commit 0369a23872
2 changed files with 131 additions and 84 deletions

View File

@@ -13,35 +13,27 @@ from osaca.param import Register, MemAddr
class Scheduler(object): class Scheduler(object):
arch_dict = {'SNB': 6, 'IVB': 6, 'HSW': 8, 'BDW': 8, 'SKL': 8, 'ZEN': 10} arch_dict = {'SNB': 6, 'IVB': 6, 'HSW': 8, 'BDW': 8, 'SKL': 8, 'ZEN': 10}
dv_port_dict = {'SKL': 0, 'ZEN': 3} dv_ports_dict = {'SKL': [0], 'ZEN': [3]}
ports = None # type: int
instrList = None # type: list<list<str,Param[,Param][,Param],str>>,
# content of most inner list in instrList: instr, operand(s), instr form # content of most inner list in instrList: instr, operand(s), instr form
df = None # type: DataFrame df = None # type: DataFrame
# for parallel ld/st in archs with 1 st/cy and >1 ld/cy, able to do 1 st and 1 ld in 1cy # for parallel ld/st in archs with 1 st/cy and >1 ld/cy, able to do 1 st and 1 ld in 1cy
ld_ports = None # type: list<int> ld_ports = None # type: list<int>
# enable flag for parallel ld/st # enable flag for parallel ld/st
en_par_ldst = False # type: boolean en_par_ldst = False # type: boolean
dv_port = -1 # type: int
def __init__(self, arch, instruction_list): def __init__(self, arch, instruction_list):
arch = arch.upper() arch = arch.upper()
try: try:
self.ports = self.arch_dict[arch] self.ports = self.arch_dict[arch]
except KeyError: except KeyError:
print('Architecture not supportet for EU scheduling.', file=sys.stderr) print('Architecture not supported for EU scheduling.', file=sys.stderr)
sys.exit(1) sys.exit(1)
# check for parallel ld/st in a cycle # check for parallel ld/st in a cycle
if arch == 'ZEN': if arch == 'ZEN':
self.en_par_ldst = True self.en_par_ldst = True
self.ld_ports = [9, 10] self.ld_ports = [9, 10]
# check for DV port # check for DV port
try: self.dv_ports = self.dv_ports_dict.get(arch, [])
self.dv_port = self.dv_port_dict[arch]
except KeyError:
# no DV port available (yet, new feature in OSACA v0.2)
# do nothing
pass
self.instrList = instruction_list self.instrList = instruction_list
# curr_dir = os.path.realpath(__file__)[:-11] # curr_dir = os.path.realpath(__file__)[:-11]
osaca_dir = os.path.expanduser('~/.osaca/') osaca_dir = os.path.expanduser('~/.osaca/')
@@ -68,11 +60,8 @@ class Scheduler(object):
sched = self.get_head() sched = self.get_head()
# Initialize ports # Initialize ports
# Add DV port, if it is existing # Add DV port, if it is existing
tmp_port = 0 occ_ports = [[0] * (self.ports + len(self.dv_ports)) for x in range(len(self.instrList))]
if self.dv_port != -1: port_bndgs = [0] * (self.ports + len(self.dv_ports))
tmp_port = 1
occ_ports = [[0] * (self.ports + tmp_port) for x in range(len(self.instrList))]
port_bndgs = [0] * (self.ports + tmp_port)
# Store instruction counter for parallel ld/st # Store instruction counter for parallel ld/st
par_ldst = 0 par_ldst = 0
# Count the number of store instr if we schedule for an architecture with par ld/st # Count the number of store instr if we schedule for an architecture with par ld/st
@@ -94,9 +83,9 @@ class Scheduler(object):
except IndexError: except IndexError:
# Instruction form not in CSV # Instruction form not in CSV
if instrForm[0][:3] == 'nop': if instrForm[0][:3] == 'nop':
sched += self.get_line(occ_ports[i], '* ' + instrForm[-1]) sched += self.format_port_occupation_line(occ_ports[i], '* ' + instrForm[-1])
else: else:
sched += self.get_line(occ_ports[i], 'X ' + instrForm[-1]) sched += self.format_port_occupation_line(occ_ports[i], 'X ' + instrForm[-1])
continue continue
occ_ports[i] = list(tup) occ_ports[i] = list(tup)
# Check if it's a ld including instr # Check if it's a ld including instr
@@ -112,11 +101,11 @@ class Scheduler(object):
occ_ports[i][port] = '(' + str(occ_ports[i][port]) + ')' occ_ports[i][port] = '(' + str(occ_ports[i][port]) + ')'
# Write schedule line # Write schedule line
if len(p_flg) > 0: if len(p_flg) > 0:
sched += self.get_line(occ_ports[i], p_flg + instrForm[-1]) sched += self.format_port_occupation_line(occ_ports[i], p_flg + instrForm[-1])
for port in self.ld_ports: for port in self.ld_ports:
occ_ports[i][port] = 0 occ_ports[i][port] = 0
else: else:
sched += self.get_line(occ_ports[i], instrForm[-1]) sched += self.format_port_occupation_line(occ_ports[i], instrForm[-1])
# Add throughput to total port binding # Add throughput to total port binding
port_bndgs = list(map(add, port_bndgs, occ_ports[i])) port_bndgs = list(map(add, port_bndgs, occ_ports[i]))
if machine_readable: if machine_readable:
@@ -150,9 +139,9 @@ class Scheduler(object):
except IndexError: except IndexError:
# Instruction form not in CSV # Instruction form not in CSV
if instrForm[0][:3] == 'nop': if instrForm[0][:3] == 'nop':
sched += self.get_line(occ_ports[i], '* ' + instrForm[-1]) sched += self.format_port_occupation_line(occ_ports[i], '* ' + instrForm[-1])
else: else:
sched += self.get_line(occ_ports[i], 'X ' + instrForm[-1]) sched += self.format_port_occupation_line(occ_ports[i], 'X ' + instrForm[-1])
continue continue
if wTP: if wTP:
# Get the occurance of each port from the occupation list # Get the occurance of each port from the occupation list
@@ -176,7 +165,7 @@ class Scheduler(object):
for j in range(0, self.ports): for j in range(0, self.ports):
occ_ports[i][j] = t_all.count(j) / variations occ_ports[i][j] = t_all.count(j) / variations
# Write schedule line # Write schedule line
sched += self.get_line(occ_ports[i], instrForm[-1]) sched += self.format_port_occupation_line(occ_ports[i], instrForm[-1])
# Add throughput to total port binding # Add throughput to total port binding
port_bndgs = list(map(add, port_bndgs, occ_ports[i])) port_bndgs = list(map(add, port_bndgs, occ_ports[i]))
return sched, port_bndgs return sched, port_bndgs
@@ -214,7 +203,7 @@ class Scheduler(object):
raise IndexError() raise IndexError()
except IndexError: except IndexError:
# Instruction form not in CSV # Instruction form not in CSV
sched += self.get_line([0] * self.ports, '* ' + instrForm[-1]) sched += self.format_port_occupation_line([0] * self.ports, '* ' + instrForm[-1])
continue continue
found = False found = False
while not found: while not found:
@@ -226,7 +215,7 @@ class Scheduler(object):
found = True found = True
good = [entry.LT.values[0] if (j in portOcc) else 0 for j in good = [entry.LT.values[0] if (j in portOcc) else 0 for j in
range(0, self.ports)] range(0, self.ports)]
sched += self.get_line(good, instrForm[-1]) sched += self.format_port_occupation_line(good, instrForm[-1])
# Add new occupation # Add new occupation
occ_ports = [occ_ports[j] + good[j] for j in range(0, self.ports)] occ_ports = [occ_ports[j] + good[j] for j in range(0, self.ports)]
break break
@@ -331,22 +320,15 @@ class Scheduler(object):
str str
String containing the header String containing the header
""" """
horiz_line = '-' * 7 * self.ports port_names = self.get_port_naming()
if self.dv_port != -1:
horiz_line += '-' * 6
horiz_line += '-\n'
port_anno = (' ' * int(math.floor((len(horiz_line) - 24) / 2)) + 'Ports Pressure in cycles'
+ ' ' * int(math.ceil((len(horiz_line) - 24) / 2)) + '\n')
port_line = ''
for i in range(0, self.ports):
port_line += '| {} '.format(i)
if i == self.dv_port:
port_line = port_line + '- DV '
port_line += '|\n'
head = port_anno + port_line + horiz_line
return head
def get_line(self, occ_ports, instr_name): port_line = ''.join('|{:^6}'.format(pn) for pn in port_names) + '|\n'
horiz_line = '-' * (len(port_line) - 1) + '\n'
port_anno = ' ' * ((len(port_line) - 25) // 2) + 'Ports Pressure in cycles\n'
return port_anno + port_line + horiz_line
def format_port_occupation_line(self, occ_ports, instr_name):
""" """
Create line with port occupation for output. Create line with port occupation for output.
@@ -363,24 +345,31 @@ class Scheduler(object):
String for output containing port scheduling for instr_name String for output containing port scheduling for instr_name
""" """
line = '' line = ''
for p_num, i in enumerate(occ_ports): for cycles in occ_ports:
pipe = '|' if cycles == 0:
if isinstance(i, str): line += '|' + ' ' * 6
cycles = i elif cycles >= 10:
i = float(i[1:-1]) line += '|{:^6.1f}'.format(cycles)
r_space = ''
else: else:
cycles = ' ' if (i == 0) else '%.2f' % float(i) line += '|{:^6.2f}'.format(cycles)
r_space = ' '
if p_num == self.dv_port + 1 and p_num != 0:
pipe = ' '
if i >= 10:
line += pipe + cycles + r_space
else:
line += pipe + ' ' + cycles + r_space
line += '| ' + instr_name + '\n' line += '| ' + instr_name + '\n'
return line return line
def get_port_naming(self):
"""
Return list of port names
:return: list of strings
"""
port_names = []
dv_ports_appended = 0
for i in range(self.ports):
port_names.append(str(i))
if i in self.dv_ports:
dv_ports_appended += 1
port_names.append(str(i)+'DV')
return port_names
def get_port_binding(self, port_bndg): def get_port_binding(self, port_bndg):
""" """
Create port binding out of scheduling result. Create port binding out of scheduling result.
@@ -395,36 +384,23 @@ class Scheduler(object):
str str
String containing the port binding graphical output String containing the port binding graphical output
""" """
sp_left, sp_right, total = self.get_spaces(port_bndg) col_widths = self.get_column_widths(port_bndg)
header = 'Port Binding in Cycles Per Iteration:\n' header = 'Port Binding in Cycles Per Iteration:\n'
horiz_line = '-' * 10 + '-' * total + '\n' horiz_line = '-' * 10 + '-' * (sum(col_widths) + len(col_widths)) + '\n'
port_line = '| Port |' port_line = '| Port |'
after_dv = 0 for i, port_name in enumerate(self.get_port_naming()):
for i in range(0, self.ports): port_line += port_name.center(col_widths[i]) + '|'
if i == self.dv_port:
port_line += ' ' * int(sp_left[i]) + str(i) + ' ' * int(sp_right[i]) + '-'
port_line += ' ' * int(sp_left[i + 1] - 1) + 'DV' + ' ' * int(sp_right[i + 1]) + '|'
after_dv = 1
else:
port_line += (' ' * int(sp_left[i + after_dv]) + str(i)
+ ' ' * int(sp_right[i + after_dv]))
port_line += '|'
port_line += '\n' port_line += '\n'
cyc_line = '| Cycles |' cyc_line = '| Cycles |'
for i in range(len(port_bndg)): for i in range(len(port_bndg)):
pipe = '|' if (i != self.dv_port) else ' ' cyc_line += '{}|'.format(str(round(port_bndg[i], 2)).center(col_widths[i]))
cyc = str(round(port_bndg[i], 2))
cyc_line += ' {} {}'.format(cyc, pipe)
cyc_line += '\n' cyc_line += '\n'
binding = header + horiz_line + port_line + horiz_line + cyc_line + horiz_line binding = header + horiz_line + port_line + horiz_line + cyc_line + horiz_line
return binding return binding
def get_spaces(self, port_bndg): def get_column_widths(self, port_bndg):
len_list = [len(str(round(x, 2))) + 1 for x in port_bndg] return [max(len(str(round(x, 2))), len(name)) + 2
total = sum([x + 2 for x in len_list]) for x, name in zip(port_bndg, self.get_port_naming())]
sp_left = [math.ceil(x / 2) for x in len_list]
sp_right = [math.floor(x / 2) for x in len_list]
return sp_left, sp_right, total
def get_operand_suffix(self, instr_form): def get_operand_suffix(self, instr_form):
""" """

View File

@@ -1,6 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import argparse import argparse
import collections
import sys import sys
import os import os
import io import io
@@ -316,7 +317,40 @@ def strip_assembly(assembly):
return '\n'.join(asm_lines) return '\n'.join(asm_lines)
# TODO replacement for instr_forms entries in OSACA
# class InstructionForm:
# def __init__(self, mnemonic, parameters, line=None):
# self.mnemonic = mnemonic
# self.parameters = parameters
# self.line = line
#
# @classmethod
# def from_assembly(cls, line):
# # Skip clang padding bytes
# while line.startswith('data32 '):
# line = line[7:]
#
# line_split = line.split()
# mnemonic = line_split[0]
# if len(line_split) > 1:
# parameters = line_split[1:]
# else:
# parameters = None
#
# return cls(mnemonic, parameters, line)
#
# def __str__(self):
# return line
#
# def __repr__(self):
# return '{}({!r}, {!r}, {!r})'.format(
# self.__class__.__name__, self.mnemonic, self.parameters, self.line)
class OSACA(object): class OSACA(object):
"""
A single OSACA analysis.
"""
srcCode = None srcCode = None
tp_list = False tp_list = False
# Variables for checking lines # Variables for checking lines
@@ -331,6 +365,13 @@ class OSACA(object):
VALID_ARCHS = ['SNB', 'IVB', 'HSW', 'BDW', 'SKL', 'ZEN'] VALID_ARCHS = ['SNB', 'IVB', 'HSW', 'BDW', 'SKL', 'ZEN']
def __init__(self, arch, assembly, extract_with_markers=True): def __init__(self, arch, assembly, extract_with_markers=True):
"""
Create and run analysis on assembly for architecture.
:param arch: architecture abbreviation
:param assembly: assembly code as string
:param extract_with_markers: if True, use markers to isolate relavent section
"""
# Check architecture # Check architecture
if arch not in self.VALID_ARCHS: if arch not in self.VALID_ARCHS:
raise ValueError("Invalid architecture ({!r}), must be one of {}.".format( raise ValueError("Invalid architecture ({!r}), must be one of {}.".format(
@@ -353,14 +394,20 @@ class OSACA(object):
# Check for database for the chosen architecture # Check for database for the chosen architecture
self.df = read_csv(arch) self.df = read_csv(arch)
# Run analysis # Run analysis and populate instr_forms
self.inspect() self.inspect()
# Create schedule
self.schedule = Scheduler(self.arch, self.instr_forms)
def inspect(self): def inspect(self):
""" """
Run analysis. Run analysis.
""" """
for line in self.assembly: for line in self.assembly:
# TODO potential replacement for instr_forms entries in OSACA
# InstructionForm.from_assembly(line)
if re.match(r'^[a-zA-Z0-9\_\.]+:$', line): if re.match(r'^[a-zA-Z0-9\_\.]+:$', line):
continue continue
self.check_instr(line) self.check_instr(line)
@@ -391,8 +438,7 @@ class OSACA(object):
param_list_types = list(param_list) param_list_types = list(param_list)
# Check operands and separate them by IMMEDIATE (IMD), REGISTER (REG), # Check operands and separate them by IMMEDIATE (IMD), REGISTER (REG),
# MEMORY (MEM) or LABEL(LBL) # MEMORY (MEM) or LABEL(LBL)
for i in range(len(param_list)): for i, op in enumerate(param_list):
op = param_list[i]
if len(op) <= 0: if len(op) <= 0:
op = Parameter('NONE') op = Parameter('NONE')
elif op[0] == '$': elif op[0] == '$':
@@ -404,10 +450,10 @@ class OSACA(object):
j = op.index('{') j = op.index('{')
opmask = True opmask = True
op = Register(op[1:j].strip(" ,"), opmask) op = Register(op[1:j].strip(" ,"), opmask)
elif '<' in op or op.startswith('.'): elif '<' in op or re.match(r'^([a-zA-Z\._]+[a-zA-Z0-9_\.]*)+$', op):
op = Parameter('LBL') op = Parameter('LBL')
else: else:
op = MemAddr(op, ) op = MemAddr(op)
param_list[i] = str(op) param_list[i] = str(op)
param_list_types[i] = op param_list_types[i] = op
# Add to list # Add to list
@@ -464,6 +510,8 @@ class OSACA(object):
""" """
Creates output of analysed file including a time stamp. Creates output of analysed file including a time stamp.
Used to interface with Kerncraft.
Parameters Parameters
---------- ----------
tp_list : bool tp_list : bool
@@ -489,17 +537,40 @@ class OSACA(object):
output += self.create_tp_list(horiz_line) output += self.create_tp_list(horiz_line)
if pr_sched: if pr_sched:
output += '\n\n' output += '\n\n'
sched = Scheduler(self.arch, self.instr_forms) sched_output, port_binding = self.schedule.new_schedule(machine_readable)
sched_output, port_binding = sched.new_schedule(machine_readable)
# if machine_readable, we're already done here # if machine_readable, we're already done here
if machine_readable: if machine_readable:
return sched_output return sched_output
binding = sched.get_port_binding(port_binding) binding = self.schedule.get_port_binding(port_binding)
output += sched.get_report_info() + '\n' + binding + '\n\n' + sched_output output += self.schedule.get_report_info() + '\n' + binding + '\n\n' + sched_output
block_tp = round(max(port_binding), 2) block_tp = round(max(port_binding), 2)
output += 'Total number of estimated throughput: {}\n'.format(block_tp) output += 'Total number of estimated throughput: {}\n'.format(block_tp)
return output return output
def get_port_occupation_cycles(self):
"""
Build dict with port names and cycles they are occupied during one block execution
Used to interface with Kerncraft.
:return: dictionary of ports and cycles
"""
sched_output, port_binding = self.schedule.new_schedule()
return collections.OrderedDict([
(port_name, port_binding[i])
for i, port_name in enumerate(self.schedule.get_port_naming())])
def get_total_throughput(self):
"""
Return total cycles estimated per block execution. Including (potential) penalties.
Used to interface with Kerncraft.
:return: float of cycles
"""
return max(self.get_port_occupation_cycles().values())
def create_horiz_sep(self): def create_horiz_sep(self):
""" """
Calculate and return horizontal separator line. Calculate and return horizontal separator line.