hqjenny-centrifuge/scripts/generate_wrapper.py

309 lines
11 KiB
Python
Executable File

#!/usr/bin/env python3
import argparse
import re
import pathlib
import collections
class MmioArg():
def __init__(self, name, addr, size=1):
self.name = name
self.addr = int(addr, 0)
self.size = size
def incrementSize(self):
if self.size < 2:
self.size += 1
else:
raise RuntimeError("Invalid size for argument " + self.name + ": " + str(self.size + 1))
def cType(self):
"""Returns a string representing the C type of this argument"""
if self.size == 0:
return "void"
elif self.size == 1:
return "uint32_t"
elif self.size == 2:
return "uint64_t"
else:
raise RuntimeError("Unsupported variable size: " + str(self.size))
def generateHeader(signature):
"""Given the signature of the accelerated function, return an appropriate
header file. """
header = ("#ifndef ACCEL_WRAPPER_H\n"
"#define ACCEL_WRAPPER_H\n")
header += '\n'
header += signature + ";\n"
header += "#endif"
return header
def ident(n):
return " "*n
def cleanRoccArg(body):
"""Cleans up a RoccArg. Returns True if the argument was cleaned and can be
used, returns False if the argument should be ignored."""
reIgnore = re.compile('ap_clk.*|ap_rst.*|\S+_req_full_n|\S+_rsp_empty_n')
reBaseName = re.compile('ap_(\S+)|(\S+)_datain')
if reIgnore.match(body):
return None
m = reBaseName.match(body)
if not m:
raise ValueError("Could not parse argument name: " + body)
return m.group(m.lastindex)
def parseVerilogRocc(vpath):
"""Parse a centrifuge-generated verilog file to extract the information
needed to generate a RoCC wrapper.
vpath: Path to main verilog function file
Returns: (inputs, retVal)
inputs - list of argument names
retVal - boolean indicating whether or not a return value is present
"""
# Input/Output statements in the verilog. We assume only one module in the file.
reInput = re.compile('^\s*input\s+\[.*:.*\]\s*(.*)')
reReturnVal = re.compile('^\s*output\s+\[(.*):(.*)\]\s*ap_return;')
print("Parsing: ",vpath)
inputs = []
retVal = False
with open(vpath, 'r') as vf:
for line in vf.readlines():
inMatch = reInput.match(line)
if inMatch:
argName = cleanRoccArg(inMatch.group(1))
if argName:
inputs.append(argName)
else:
if reReturnVal.match(line):
retVal = True
return inputs, retVal
def parseVerilogTL(vpath):
"""Parse a centrifuge-generated verilog file to extract the information
needed to generate tilelink wrappers.
vpath: Path to the verilog file containing control signal info (path-like object)
returns: (returnSize, Args)
retVal: MmioArg representing the return value (or None if no return).
Args: List of MmioArg representing the arguments to the accelerated function
"""
with open(vpath, 'r') as vf:
print("Parsing: ",vpath)
reStart = re.compile("^//------------------------Address Info------------------")
reEnd = re.compile("^//------------------------Parameter----------------------")
reAddr = re.compile("(0x\S+) : Data signal of (\S+)")
inHeader = False
args = collections.OrderedDict()
retVal = None
for line in vf.readlines():
if not inHeader:
if reStart.match(line):
inHeader = True
else:
if reEnd.match(line):
break
else:
m = reAddr.search(line)
if m:
name = m.group(2)
addr = m.group(1)
if name == 'ap_return':
if retVal is None:
retVal = MmioArg(name, addr)
else:
retVal.incrementSize()
elif name in args:
args[name].incrementSize()
else:
args[name] = MmioArg(name, addr)
return (retVal, list(args.values()))
def generateWrapperRocc(fname, roccIdx, inputs, retVal):
"""Returns a Rocc C wrapper given a function.
fname - name of the function
inputs - list of argument names
retVal - boolean indicating whether or not a value is returned
"""
# current indentation level
lvl = 0
cWrapper = ('#include "rocc.h"\n'
'\n'
'#define ACCEL_WRAPPER\n'
'#include "accel.h"\n'
'\n')
cWrapper += ident(lvl) + "#define XCUSTOM_ACC " + roccIdx + "\n"
cWrapper += "\n"
signature = ""
if retVal:
retStr = "uint64_t"
else:
retStr = "void"
signature += retStr + " " + fname + "("
argStrs = []
for arg in inputs:
argStrs.append("uint64_t " + arg)
signature += ", ".join(argStrs)
signature += ")"
cWrapper += signature + "\n"
cWrapper += "{\n"
lvl = 1
if retVal:
cWrapper += ident(lvl) + "uint64_t ret_val;\n"
cWrapper += "\n"
if len(inputs) == 0:
cWrapper += ident(lvl) + "ROCC_INSTRUCTION_D(XCUSTOM_ACC, ret_val, 0);\n"
elif len(inputs) == 1:
cWrapper += ident(lvl) + "ROCC_INSTRUCTION_DS(XCUSTOM_ACC, ret_val, " + inputs[0] + ", 0);\n"
elif len(inputs) == 2:
cWrapper += ident(lvl) + "ROCC_INSTRUCTION_DSS(XCUSTOM_ACC, ret_val, " + inputs[0] + ", " + inputs[1] + ", 0);\n"
else:
raise ValueError("Too many inputs. Rocc only supports up to 2 arguments, was passed " + len(inputs))
else:
if len(inputs) == 0:
cWrapper += ident(lvl) + "ROCC_INSTRUCTION(XCUSTOM_ACC, 0);\n"
elif len(inputs) == 1:
cWrapper += ident(lvl) + "ROCC_INSTRUCTION_S(XCUSTOM_ACC, " + inputs[0] + ", 0);\n"
elif len(inputs) == 2:
cWrapper += ident(lvl) + "ROCC_INSTRUCTION_SS(XCUSTOM_ACC, " + inputs[0] + ", 0);\n"
else:
raise ValueError("Too many inputs. Rocc only supports up to 2 arguments, was passed " + len(inputs))
cWrapper += ident(lvl) + "ROCC_BARRIER();\n"
cWrapper += '\n'
if retVal:
cWrapper += ident(lvl) + "return ret_val;\n"
cWrapper += "}"
return cWrapper, generateHeader(signature)
def generateWrapperTL(fname, baseAddr, retVal, args):
"""Given a set of mmio address/varialble pairs, produce the C wrapper
(returned as a string).
fname: Name to use for the function
baseAddr: MMIO base address to use
retVal: MmioArg representing the return value (may be None)
args: List of MmioArg representing the function inputs
"""
cWrapper = ('#include "mmio.h"\n'
'#define ACCEL_WRAPPER\n'
'#include "accel.h"\n'
'\n'
'#define AP_DONE_MASK 0b10\n')
# MMIO Constants
cWrapper += "#define ACCEL_BASE " + str(baseAddr) + "\n"
cWrapper += "#define ACCEL_INT 0x4\n"
for arg in args + ([retVal] if retVal is not None else []):
cWrapper += "#define ACCEL_"+arg.name+"_0 "+ hex(arg.addr) + "\n"
if arg.size == 2:
cWrapper += "#define ACCEL_"+arg.name+"_1 " + hex(arg.addr + 0x4) + "\n"
cWrapper += "\n"
# Create the function signature
signature = ""
if retVal is None:
retStr = "void"
else:
retStr = retVal.cType()
signature += retStr + " " + fname + "("
argStrs = []
for arg in args:
argStrs.append(arg.cType() + " " + arg.name)
signature += ", ".join(argStrs)
signature += ")"
cWrapper += signature + "\n"
cWrapper += "{\n"
# Pass Args to MMIO
cWrapper += " //Disable Interrupts\n"
cWrapper += " reg_write32(ACCEL_BASE + ACCEL_INT, 0x0);\n"
for arg in args:
cWrapper += " reg_write32(ACCEL_BASE + ACCEL_"+arg.name+"_0, (uint32_t) "+arg.name+");\n"
if arg.size == 2:
cWrapper += " reg_write32(ACCEL_BASE + ACCEL_"+arg.name+"_1, (uint32_t) ("+arg.name+" >> 32));\n"
# Execute Accelerator
cWrapper += (" // Write to ap_start to start the execution \n"
" reg_write32(ACCEL_BASE, 0x1);\n"
"\n"
" // Done?\n"
" int done = 0;\n"
" while (!done){\n"
" done = reg_read32(ACCEL_BASE) & AP_DONE_MASK;\n"
" }\n")
# Handle returns (if any)
if retVal is not None:
cWrapper += "\n"
cWrapper += " " + retVal.cType() + " ret_val = 0;\n"
cWrapper += " ret_val = reg_read32(ACCEL_BASE + ACCEL_"+retVal.name+"_0);\n"
if retVal.size == 2:
cWrapper += " ret_val |= reg_read32(ACCEL_BASE + ACCEL_"+retVal.name+"_1) >> 32;\n"
cWrapper += "}"
return cWrapper, generateHeader(signature)
if __name__ == '__main__':
parser = argparse.ArgumentParser(
description="Generate software wrappers for a given centrifuge-generated function.")
parser.add_argument('-n', '--fname', required=True, help="Name of function to accelerate")
parser.add_argument('-b', '--base', required=True, help="Base address of function (if tilelink), RoCC index (if rocc)")
parser.add_argument('-p', '--prefix', default="", help="Optional prefix for function")
parser.add_argument('-m', '--mode', required=True,
help="Function integration mode (either 'tl' or 'rocc')")
parser.add_argument('-s', '--source', required=True, type=pathlib.Path,
help="Path to the source directory to use when generating (e.g. 'centrifuge/accel/hls_example_func/').")
args = parser.parse_args()
if args.mode == 'tl':
retVal, funcArgs = parseVerilogTL(
args.source / 'src' / 'main' / 'verilog' / (args.prefix + args.fname + "_control_s_axi.v"))
cWrapper, hWrapper = generateWrapperTL(args.fname, args.base, retVal, funcArgs)
elif args.mode == 'rocc':
inputs, retVal = parseVerilogRocc(
args.source / 'src' / 'main' / 'verilog' / (args.prefix + args.fname + ".v"))
cWrapper, hWrapper = generateWrapperRocc(args.fname, args.base, inputs, retVal)
else:
raise NotImplementedError("Mode '" + args.mode + "' not supported.")
with open(args.source / 'src' / 'main' / 'c' / 'accel_wrapper.c', 'w') as cF:
cF.write(cWrapper)
with open(args.source / 'src' / 'main' / 'c' / 'accel_wrapper.h', 'w') as hF:
hF.write(hWrapper)