diff --git a/.gitignore b/.gitignore index 761d75c..5a569ba 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *.o *.a cf-config.yaml +*.swp diff --git a/deploy/centrifuge.py b/deploy/centrifuge.py index 74675af..19a53cb 100755 --- a/deploy/centrifuge.py +++ b/deploy/centrifuge.py @@ -15,6 +15,7 @@ import pathlib # centrifuge import pkg.util as util import pkg.buildaccel as buildaccel +import pkg.buildsw as buildsw from os.path import dirname as up @@ -81,6 +82,8 @@ def main(args): buildaccel.generate_hw(accel_config) elif args.task == 'clean_hw': buildaccel.clean_hw(accel_config) + elif args.task == 'generate_sw': + buildsw.generateSW(accel_config) else: print("Command: " + str(args.task) + " not yet implemented") diff --git a/deploy/pkg/buildsw/__init__.py b/deploy/pkg/buildsw/__init__.py new file mode 100644 index 0000000..9d914c4 --- /dev/null +++ b/deploy/pkg/buildsw/__init__.py @@ -0,0 +1 @@ +from .generate_wrapper import * diff --git a/deploy/pkg/buildsw/generate_wrapper.py b/deploy/pkg/buildsw/generate_wrapper.py new file mode 100755 index 0000000..f5a23f8 --- /dev/null +++ b/deploy/pkg/buildsw/generate_wrapper.py @@ -0,0 +1,351 @@ +#!/usr/bin/env python3 +import argparse +import re +import pathlib +import collections +import logging +from .. import util + +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(level): + """Return a string of spaces representing an indentation to level 'level'""" + 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 (list(args.values()), retVal) + +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, args, retVal): + """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) + +def generateSW(accels): + """Generate all software wrappers for the specified set of accelerators (util.AccelConfig)""" + logger = logging.getLogger() + + # Create the directory for generated outputs. We may overwrite things in + # this directory as-needed. + accels.gensw_dir.mkdir(exist_ok=True) + + for accel in accels.rocc_accels: + inputs, retVal = parseVerilogRocc(accel.verilog_dir / (accel.name + ".v")) + cWrapper, hWrapper = generateWrapperRocc(accel.func, accel.rocc_insn_id, inputs, retVal) + accel.wrapper_dir = accels.gensw_dir / accel.name + accel.wrapper_dir.mkdir(exist_ok=True) + + cPath = accel.wrapper_dir / (accel.name + '_rocc_wrapper.c') + cPath.unlink(missing_ok=True) + with open(cPath , 'w') as cF: + cF.write(cWrapper) + + hPath = accel.wrapper_dir / (accel.name + '_rocc_wrapper.h') + hPath.unlink(missing_ok=True) + with open(hPath , 'w') as hF: + hF.write(hWrapper) + + for accel in accels.tl_accels: + funcArgs, retVal = parseVerilogTL(accel.verilog_dir / (accel.name + "_control_s_axi.v")) + cWrapper, hWrapper = generateWrapperTL(accel.func, accel.base_addr, funcArgs, retVal) + accel.wrapper_dir = accels.gensw_dir / accel.name + accel.wrapper_dir.mkdir(exist_ok=True) + + cPath = accel.wrapper_dir / (accel.name + '_tl_wrapper.c') + cPath.unlink(missing_ok=True) + with open(cPath , 'w') as cF: + cF.write(cWrapper) + + hPath = accel.wrapper_dir / (accel.name + '_tl_wrapper.h') + hPath.unlink(missing_ok=True) + with open(hPath , 'w') as hF: + hF.write(hWrapper) + +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': + funcArgs, retVal = parseVerilogTL( + args.source / 'src' / 'main' / 'verilog' / (args.prefix + args.fname + "_control_s_axi.v")) + + cWrapper, hWrapper = generateWrapperTL(args.fname, args.base, funcArgs, retVal) + 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) + diff --git a/deploy/pkg/util/parseconfig.py b/deploy/pkg/util/parseconfig.py index be472f0..652da9d 100644 --- a/deploy/pkg/util/parseconfig.py +++ b/deploy/pkg/util/parseconfig.py @@ -60,6 +60,7 @@ class AccelConfig: self.accel_name = self.accel_json_path.stem self.accel_json_dir = accel_json_path.parent + self.gensw_dir = self.accel_json_dir / 'centrifuge_wrappers' self.hw_accel_dir = genhw_dir / self.accel_name self.accel_json = self.parse_json(self.accel_json_path) self.rocc_accels = []