Commit d7e0b6ba authored by Thomas Lange's avatar Thomas Lange
Browse files

+add: initial commit (benchmark environment, first benchmark circuit, install...

+add: initial commit (benchmark environment, first benchmark circuit, install and setup guide, xilinx tool scripts and anaconda install and environment scripts).
parent ecf79fab
################################################################################
# title : benchmark.py
# description : Main class to create and start a benchmark. The class needs
# the location of a project settings file (json) and a
# benchmark setting file (json).
# author : Thomas Lange
# email : thomas.lange@esa.int
# usage : import benchmark
# python_version : 3.6
################################################################################
# import modules
import os
import itertools
import json
import testCase
import progressBar
# define class
class Benchmark(object):
# Base directory for the scripts (relative to this file)
prjBaseDir = os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir)
# Settings file which defines the different CAD tools specific design flows (json file)
designFlowSettingsFile = os.path.join(prjBaseDir, 'tools', 'designFlowSettings.json')
# Settings file which defines the different CAD tools specific design flows (json file)
deviceSettingsFile = os.path.join(prjBaseDir, 'tools', 'deviceSettings.json')
# Initialize the benchmark. Create test cases device list.
def __init__(self, prjSettingsFile, benchmarkSettingsFile, enableOutput = False):
# set the given project settings file
self.prjSettingsFile = prjSettingsFile
# set the given benchmark settings file
self.benchmarkSettingsFile = benchmarkSettingsFile
# open and read benchmark settings file
with open(self.benchmarkSettingsFile) as jsonFile:
benchmarkSettings = json.load(jsonFile)
# set project name for benchmark (if defined)
if "name" not in benchmarkSettings:
self.prjName = None
else:
self.prjName = benchmarkSettings["name"]
# create progress bar
if self.prjName != None:
self.progBar = progressBar.ProgressBar(
self.prjName + ' - Synthesis Benchmark'
)
else:
self.progBar = progBar.ProgressBar('Synthesis Benchmark')
self.progBar.update(0, 0, "Initialise Project")
# create default test case (with unmodified generic setting)
self.defaultTestCase = testCase.TestCase(prjSettingsFile, None, enableOutput)
# create list of generic settings
if "genericSettings" in benchmarkSettings:
# create a flattened generic list where dict keys are tuple of
# filename and generic name. convert keys and values into string.
flattenedGenericList = {}
for hdlFile in benchmarkSettings["genericSettings"]:
for generic in benchmarkSettings["genericSettings"][hdlFile]:
if isinstance(benchmarkSettings["genericSettings"][hdlFile][generic], list):
flattenedGenericList[str(hdlFile), str(generic)] = \
[str(x) for x in benchmarkSettings["genericSettings"][hdlFile][generic]]
else:
flattenedGenericList[str(hdlFile), str(generic)] = \
[str(benchmarkSettings["genericSettings"][hdlFile][generic])]
# create all possible combinations (cartesian product) of generics
flattenedGenericListProd = \
list(
dict(zip(flattenedGenericList, x)) for x in
itertools.product(*flattenedGenericList.values())
)
# recreate nested dictionary and create objects for the test cases
genericSettingList = []
for genericSetting in flattenedGenericListProd:
newGenericSetting = {}
for item in genericSetting:
hdlFile = item[0]
generic = item[1]
if hdlFile in newGenericSetting:
newGenericSetting[hdlFile][generic] = genericSetting[item]
else:
newGenericSetting[hdlFile] = {}
newGenericSetting[hdlFile][generic] = genericSetting[item]
genericSettingList.append(newGenericSetting)
# create test cases for the different generic settings
self.testCaseList = []
for setting in genericSettingList:
self.testCaseList.append(
testCase.TestCase(prjSettingsFile, setting, enableOutput)
)
else:
# set list to None when no generic settings are defined
self.testCaseList = None
########################################################################
# create list of devices and design flows
########################################################################
# open and read design flows from settings file
with open(self.designFlowSettingsFile) as jsonFile:
designFlows = json.load(jsonFile)
# open and read target devices from settings file
with open(self.deviceSettingsFile) as jsonFile:
devices = json.load(jsonFile)
# create a device list with device, synthesis tool and its version
def getDesignFlow(device, flowName):
if isinstance(flowName, list):
flowList = []
for flow in flowName:
flowList = flowList + getDesignFlow(device, flow)
return flowList
if isinstance(flowName, str):
return [( str(device), str(flowName) )]
else:
return []
self.deviceList = []
for device in devices:
self.deviceList = self.deviceList + getDesignFlow(device, devices[device]["tools"])
# start(): synthesises the circuit on all devices
def start(self):
totalTestCases = len(self.testCaseList)
totalDevices = len(self.deviceList)
totalRuns = totalTestCases*totalDevices
for currTestRun, testCase in enumerate(self.testCaseList):
for currDeviceRun, device in enumerate(self.deviceList):
# update progress bar
self.progBar.update(
currTestRun*(totalDevices) + currDeviceRun,
totalRuns, [
"Synthesise test case " + str(currTestRun+1)
+ " (out of " + str(totalTestCases) + ") ",
"on " + str(device[0])
+ " with " + str(device[1])
+ " (" + str(currDeviceRun+1) + " out of " + str(totalDevices) + ")"
]
)
# run synthesis of test case on specified device with specified synthesis tool
testCase.runTest(device[1], device[0])
# benchmark finished update progress bar
self.progBar.update(totalRuns, totalRuns, "Done")
def printResults(self):
print("TODO: Print results...")
################################################################################
# title : fileIO.py
# description : Class with all file related functions (reading and writing
# files, parsing generics).
# author : Thomas Lange
# email : thomas.lange@esa.int
# usage : import fileIO
# python_version : 3.6
################################################################################
# import modules
import os
import sys
import shutil # to copy files
import re # for regular expressions
# define class
class FileIO(object):
def __init__(self, genericSetting):
self.genericSetting = genericSetting
if genericSetting == None:
return
self.hdlFileLocations = genericSetting.keys()
self.hdlFiles = {}
for currFile in self.hdlFileLocations:
if os.path.isfile(currFile):
self.hdlFiles[currFile] = self.readFile(currFile)
else:
raise ValueError('File ' + currFile + ' does not exist.')
# readFile() : Read a file and return the lines
def readFile(self, fileLocation):
with open(fileLocation) as file:
fileLines = file.readlines()
return fileLines
# writeFile() : Write lines to a given file
def writeFile(self, fileLocation, fileLines):
with open(fileLocation, 'w') as file:
file.writelines(fileLines)
# backupFile() : Copy given file to make a backup
def backupFile(self, fileLocation):
shutil.copy(fileLocation, fileLocation+".bak")
# restoreFile() : Restore file from previous backup
def restoreFile(self, fileLocation):
shutil.copy(fileLocation+".bak", fileLocation)
# parseGenerics() : Parse file for generics and return dict with their names,
# types and default values. Remove generic block from given hdl file.
def parseGenerics(self, hdlFile):
# define regular expression
# to find the end of an entity description
reEntityBlockEnd = re.compile('\s*end entity.*', re.IGNORECASE)
# to find the start of a generic description
reGenericBlockStart = re.compile('\s*generic\s*\(', re.IGNORECASE)
# to find code comments
reCodeComment = re.compile('.*--(?P<comment>.*)')
# to find the generics
reGeneric = re.compile('(?P<name>\w+):(?P<type>\w+):=(?P<default>.*)')
# to find the start of the port description
rePortBlockStart = re.compile('\s*port\s*\(', re.IGNORECASE)
genericLineList = []
inGenericBlock = False
# identify lines containing generics
for line in hdlFile:
# stop searching if entity description ends or port description starts
entityBlockEnd = reEntityBlockEnd.match(line)
portBlockStart = rePortBlockStart.match(line)
if entityBlockEnd or portBlockStart:
break
# check if generic description starts
genericBlockStart = reGenericBlockStart.match(line)
if genericBlockStart:
inGenericBlock = True
# if inside of the generic description append generic lines to list
if inGenericBlock:
if not line.isspace():
genericLineList.append(line)
# remove generic description from original source file
for line in genericLineList:
hdlFile.remove(line)
# convert list of lines into one string (+ remove comments)
genericBlockString = ''
for line in genericLineList:
codeComment = reCodeComment.match(line)
if codeComment:
line = line.replace('--' + codeComment.group('comment'), '')
genericBlockString += line
# cleanup of generic block string
# remove newlines
genericBlockString = genericBlockString.replace('\n', '')
# remove spaces
genericBlockString = genericBlockString.replace(' ', '')
# remove tabs
genericBlockString = genericBlockString.replace('\t', '')
# remove the leading 'generic('
genericBlockString = genericBlockString.replace('generic(', '')
# remove the trailing ');'
genericBlockString = genericBlockString[:-2]
# generate dict with generics
genericListString = genericBlockString.split(';')
genericDict = {}
for genericItem in genericListString:
generic = reGeneric.match(genericItem)
if generic:
genericDict[generic.group('name')] = [
generic.group('type'), generic.group('default')
]
# return dict with generics
return genericDict
# setNewGenerics() : Parse file for generics and return dict with their name,
# type and default value. Remove generic block from given hdl file.
def setNewGenerics(self, hdlFile, genericSetting):
# define regular expression
# to find the start of the entity description
reEntityBlockStart = re.compile('\s*entity.*is', re.IGNORECASE)
# reconstruct generic block for given generic setting
# start generic block
newGenericBlock = ['\n', ' generic (\n']
for genericItem, values in genericSetting.items():
if values[0].lower() == "string":
newGenericBlock.append(' ' + genericItem + ' : ' + values[0]
+ ' := "' + values[1] + '";\n')
else:
newGenericBlock.append(' ' + genericItem + ' : ' + values[0]
+ ' := ' + values[1] + ';\n')
# remove last ";"
newGenericBlock[-1] = newGenericBlock[-1][:-2] + '\n'
# close generic block
newGenericBlock.append(' );\n')
# merge generic block string list into source file
for num, line in enumerate(hdlFile, 0):
entityBlockStart = reEntityBlockStart.match(line)
if entityBlockStart:
hdlFile = hdlFile[:num+1] + newGenericBlock + hdlFile[num+1:]
return hdlFile
# setGenericSetup() : Set the generics with current setup
def setGenericSetup(self):
if self.genericSetting == None:
return
# Set the generics for every file
for currFile in self.hdlFiles:
# parse generics from file
fileGenerics = self.parseGenerics(self.hdlFiles[currFile])
newFileGenerics = self.genericSetting[currFile]
for currGeneric in newFileGenerics:
if currGeneric in fileGenerics:
fileGenerics[currGeneric][1] = str(newFileGenerics[currGeneric])
else:
raise ValueError('Could not find generic ' + currGeneric
+ ' in file ' + currFile)
self.hdlFiles[currFile] = self.setNewGenerics(self.hdlFiles[currFile], fileGenerics)
self.backupFile(currFile)
self.writeFile(currFile, self.hdlFiles[currFile])
# restoreFiles() : restore all changed files
def restoreFiles(self):
# restore every file
for currFile in self.hdlFiles:
self.restoreFile(currFile)
################################################################################
# title : processMonitor.py
# description : Class which measures the time and memory usage of a process.
# author : Thomas Lange
# email : thomas.lange@esa.int
# usage : import processMonitor
# python_version : 3.6
################################################################################
# import modules
import os
import sys
import subprocess
import shlex
import time
import psutil
# define class
class ProcessMonitor:
def __init__(self, command, pollTime=0.5):
# set command which will be executed
self.command = command
# set polling time while command is running
self.pollTime = pollTime
self.execution_state = False
# execute(): Execute the command set during init
def execute(self, enableOutput=False):
# init max memory usage with zero
self.max_vms_memory = 0
self.max_rss_memory = 0
# set current time at start
self.tStart = time.time()
self.tEnd = None
# run command in subprocess (with or without output enabled)
if not enableOutput:
self.proc = subprocess.Popen(
shlex.split(self.command),
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
shell=False
)
else:
self.proc = subprocess.Popen(
shlex.split(self.command),
shell=False
)
# set execution state to true while command is running
self.execution_state = True
# when polling is active measure time and memory every pollTime seconds
if self.pollTime > 0.0:
while self.poll():
time.sleep(self.pollTime)
else:
self.tEnd = self.tStart
# set process return code and messages
self.returnCode = self.proc.returncode
self.out, self.err = self.proc.communicate()
# calculate needed time
self.tEnd = time.time()
self.procTime = self.tEnd - self.tStart
# poll(): measure process' memory usage
def poll(self):
if not self.check_execution_state():
return False
try:
# get process ID
ppid = psutil.Process(self.proc.pid)
# obtain list of the subprocess and all its descendants
descendants = list(ppid.children(recursive=True))
descendants = descendants + [ppid]
rss_memory = 0
vms_memory = 0
# calculate and sum up the memory of the subprocess and all its descendants
for descendant in descendants:
try:
mem_info = descendant.memory_info()
rss_memory += mem_info[0]
vms_memory += mem_info[1]
except psutil.NoSuchProcess:
# pass when subprocess descendant already terminated
pass
# calculate max memory usage
self.max_vms_memory = max(self.max_vms_memory, vms_memory)
self.max_rss_memory = max(self.max_rss_memory, rss_memory)
except psutil.NoSuchProcess:
return self.check_execution_state()
return self.check_execution_state()
# is_running(): check if process is still running
def is_running(self):
return psutil.pid_exists(self.proc.pid) and self.proc.poll() == None
# check_execution_state(): check process' execution state and set execution_state
# variable accordingly
def check_execution_state(self):
if not self.execution_state:
return False
if self.is_running():
return True
self.executation_state = False
return False
# close(): kill subprocess
def close(self, kill=False):
try:
ppid = psutil.Process(self.proc.pid)
if kill:
ppid.kill()
else:
ppid.terminate()
except psutil.NoSuchProcess:
pass
################################################################################
# title : progressBar.py
# description : Class to create a progress bar for the terminal.
# author : Thomas Lange
# email : thomas.lange@esa.int
# usage : import progressBar.py
# python_version : 3.6
################################################################################
# import modules
import sys, re
# class definition
class TerminalController:
"""
A class that can be used to portably generate formatted output to
a terminal.
`TerminalController` defines a set of instance variables whose
values are initialized to the control sequence necessary to
perform a given action. These can be simply included in normal
output to the terminal:
>>> term = TerminalController()
>>> print 'This is '+term.GREEN+'green'+term.NORMAL
Alternatively, the `render()` method can used, which replaces
'${action}' with the string required to perform 'action':
>>> term = TerminalController()
>>> print term.render('This is ${GREEN}green${NORMAL}')
If the terminal doesn't support a given action, then the value of
the corresponding instance variable will be set to ''. As a
result, the above code will still work on terminals that do not
support color, except that their output will not be colored.
Also, this means that you can test whether the terminal supports a
given action by simply testing the truth value of the
corresponding instance variable:
>>> term = TerminalController()
>>> if term.CLEAR_SCREEN:
... print 'This terminal supports clearning the screen.'
Finally, if the width and height of the terminal are known, then
they will be stored in the `COLS` and `LINES` attributes.
"""
# Cursor movement:
BOL = '' #: Move the cursor to the beginning of the line
UP = '' #: Move the cursor up one line
DOWN = '' #: Move the cursor down one line
LEFT = '' #: Move the cursor left one char
RIGHT = '' #: Move the cursor right one char
# Deletion:
CLEAR_SCREEN = '' #: Clear the screen and move to home position
CLEAR_EOL = '' #: Clear to the end of the line.
CLEAR_BOL = '' #: Clear to the beginning of the line.
CLEAR_EOS = '' #: Clear to the end of the screen
# Output modes:
BOLD = '' #: Turn on bold mode
BLINK = '' #: Turn on blink mode
DIM = '' #: Turn on half-bright mode
REVERSE = '' #: Turn on reverse-video mode
NORMAL = '' #: Turn off all modes
# Cursor display:
HIDE_CURSOR = '' #: Make the cursor invisible
SHOW_CURSOR = '' #: Make the cursor visible
# Terminal size:
COLS = None #: Width of the terminal (None for unknown)
LINES = None #: Height of the terminal (None for unknown)
# Foreground colors:
BLACK = BLUE = GREEN = CYAN = RED = MAGENTA = YELLOW = WHITE = ''
# Background colors:
BG_BLACK = BG_BLUE = BG_GREEN = BG_CYAN = ''
BG_RED = BG_MAGENTA = BG_YELLOW = BG_WHITE = ''
_STRING_CAPABILITIES = """
BOL=cr UP=cuu1 DOWN=cud1 LEFT=cub1 RIGHT=cuf1
CLEAR_SCREEN=clear CLEAR_EOL=el CLEAR_BOL=el1 CLEAR_EOS=ed BOLD=bold
BLINK=blink DIM=dim REVERSE=rev UNDERLINE=smul NORMAL=sgr0
HIDE_CURSOR=cinvis SHOW_CURSOR=cnorm""".split()
_COLORS = """BLACK BLUE GREEN CYAN RED MAGENTA YELLOW WHITE""".split()
_ANSICOLORS = "BLACK RED GREEN YELLOW BLUE MAGENTA CYAN WHITE".split()
def __init__(self, term_stream=sys.stdout):
"""
Create a `TerminalController` and initialize its attributes
with appropriate values for the current terminal.
`term_stream` is the stream that will be used for terminal
output; if this stream is not a tty, then the terminal is
assumed to be a dumb terminal (i.e., have no capabilities).
"""
# Curses isn't available on all platforms
try: import curses
except: return
# If the stream isn't a tty, then assume it has no capabilities.
if not term_stream.isatty(): return
# Check the terminal type. If we fail, then assume that the
# terminal has no capabilities.
try: curses.setupterm()
except: return