diff --git a/scripts/build_env.py b/scripts/build_env.py new file mode 100644 index 0000000..e73d523 --- /dev/null +++ b/scripts/build_env.py @@ -0,0 +1,70 @@ +import os +from re import split +import subprocess + +def getCfFileId(std: str): + return "08" if std == "08" else "93" ## Weird behaviour from GHDL, but all vhdl versions besides 08 have [...]93.cf + +def ghdlEnvExists(std, lib): + ## Check if work exists + try: + os.lstat("work") + except: + return False + ## Check that work is writable + if not os.access("work", os.W_OK): + print("work is write-protected, please acquire correct permissions") + return False + cfFileExists = False + filesInWork = os.listdir("work") + cfFileId = getCfFileId(std) + for file in filesInWork: + if ".cf" in file and lib in file and cfFileId in file: + cfFileExists = True + if not cfFileExists: + return False + ## Nothing bad, continue + return True + +def createBuildEnv(std: str, lib: str): + if ghdlEnvExists(std=std, lib=lib): + print("Build environment already exists, exiting...") + return -1 + ## Create build env + print("Initializing GHDL project in current directory...") + os.makedirs("work",exist_ok=True) + addAllVHDLFiles(std=std, lib=lib, init=True) + return 0 + +def addAllVHDLFiles(std: str, lib: str, init=False): + ## Ensure everything is ready for adding files + ## (init exception to avoid one if-case in ghdlEnvExists) + if not ghdlEnvExists(std=std, lib=lib) and not init: + return -1 + vhdlFiles = [] + currentlyAdded = [] + absWorkDir = "work" + cfFileId = getCfFileId(std) + ## Find already present files + if not init: + cfFileName = list(filter(lambda x: ".cf" in x and lib in x and cfFileId in x, os.listdir(absWorkDir)))[0] + cfFilePath = os.path.join(absWorkDir,cfFileName) + currentlyAdded = getCurrentlyAddedFiles(cfFilePath) + print(currentlyAdded) + ## Add files not added + for file in os.listdir(): + if ".vhd" in file and file not in currentlyAdded: + vhdlFiles.append(file) + if len(vhdlFiles) > 0: + print(f"Detected new files. Adding {vhdlFiles}") + command = ["ghdl", "-i", "--workdir=work", f"--work={lib}", f"--std={std}"] + vhdlFiles + subprocess.run(command) + return 0 + +def getCurrentlyAddedFiles(cfFilePath:str): + f = open(cfFilePath,"r") + lines = f.readlines() + f.close() + fileLines = filter(lambda x: "file" in x, lines) + files = map(lambda x: split("\"",x)[1], fileLines) + return list(files) diff --git a/scripts/elab.py b/scripts/elab.py new file mode 100644 index 0000000..793a89e --- /dev/null +++ b/scripts/elab.py @@ -0,0 +1,46 @@ +import os +import subprocess +import build_env +from typing import List +from project_man import getLibrariesPresent, getLibrariesInProject + +def generateIncludesForGHDL(includes: List[str]): + cmd = [] + [exists, projectLibs] = getLibrariesInProject() + if not exists: + return [] + for lib in projectLibs.keys(): + includeString = f"{projectLibs[lib]['path']}/work" + cmd.append(f"-P{includeString}") + for inc in includes: + includeString = f"{inc}/work" + cmd.append(f"-P{includeString}") + return cmd + +def elabDesign(topDef: str, arch: str, lib: str, std: str, includes: List[str]): + ## Add all source files present in pwd + if build_env.addAllVHDLFiles(std, lib) == -1: + print("Adding files failed. GHDL Build environment may be broken...") + return -1 + incs = generateIncludesForGHDL(includes) + command = [ + "ghdl", "-m", "--workdir=work", f"--work={lib}", f"--std={std}"] + incs + ["-o", f"work/{topDef}-{arch}", f"work.{topDef}", f"{arch}"] + subprocess.run(command) + +def runDesign(topDef: str, arch: str, lib: str, std: str, includes): + ## elaborate first, then run + if elabDesign(topDef, arch, lib, std, includes) == -1: + print("Elaboration failed...") + return -1 + os.makedirs("wave",exist_ok=True) + wavePath = os.path.join(os.getcwd(), "wave") + incs = generateIncludesForGHDL(includes) + command = [ ## may add -v for verbose + "ghdl", "--elab-run", f"--workdir=work", f"--work={lib}", f"--std={std}"] + incs + ["-o", f"work/{topDef}-{arch}", f"{topDef}", f"{arch}", + f"--wave=wave/{topDef}-{arch}.ghw" ##, "--read-wave-opt= "tuple[bool, str]": + [exists, projectFile] = findProjectFile() + if not exists: + return (False, "") + return (True, "/".join(projectFile.split("/")[0:-1])) + +def getRelativePathToRoot(path: str) -> str: + (exists, projectRoot) = findProjectRoot() + if not exists: + return "" + return os.path.relpath(path, projectRoot) + +def findProjectFile() -> "tuple[bool, str]": + cwd = os.getcwd().split("/") + for i in range(len(cwd) - 2): + searchPath = "/".join(cwd[0:len(cwd)-i]) + [exists, pathToProjectFile] = projectFileExists(searchPath) + if exists: + return (True, pathToProjectFile) + return (False, "") + +def projectFileExists(path: str) -> "tuple[bool, str]": + files = os.listdir(path) + for file in files: + if "gantry.toml" in file: + return (True, os.path.join(path, file)) + return (False,"") + +def initProjectFile(projectName: str) -> "tuple[bool, str]": + cwd = os.getcwd() + projectPath = os.path.join(cwd, "gantry.toml") + [exists, existingProjectPath] = projectFileExists(cwd) + if exists: + existingProjectName = existingProjectPath.split("/")[-1] + return (False, f"Project {existingProjectName} already exists, amend it to fit your intention or delete it to create a new project") + try: + with open(projectPath, "w") as f: + parsedTOML = toml.loads(createProjectFileTemplate(projectName)) + toml.dump(parsedTOML, f) + except: + return (False, "Creation of file failed, permissions may be set wrong") + + return (True, projectPath) + +def loadProjectFile() -> "tuple[bool, str | dict[str, Any]]" : + [exists, path] = findProjectFile() + if not exists: + return (False, "") + try: + with open(path, "r") as f: + toml.load + parsedTOML = toml.load(f) + return (True, parsedTOML) + except: + return (False, "Reading Project file failed, permissions may be set wrong") + + + +def writeProjectFile(projectDict: "dict[str, Any]") -> "tuple[bool, str]": + [exists, path] = findProjectFile() + if not exists: + return (False, "") + try: + with open(path, "w") as f: + toml.dump(projectDict, f) + return (True, "") + except: + return (False, "Reading Project file failed, permissions may be set wrong") + +def getProjectDict() -> "tuple[bool, dict[str, Any]]": + [exists, output] = loadProjectFile() + if not exists: + print(output) + return (False, {}) + if isinstance(output, dict): + return (True, output) + else: + print(output) + return (False, {}) + +def removeLibraryInProject(lib: str) -> "tuple[bool, str]": + [exists, projectDict] = getProjectDict() + if not exists: + return (False, "Found no project dictionary") + if "libraries" not in projectDict.keys(): + return (False, "No libraries are declared in this project.") + if lib in projectDict["libraries"].keys(): + projectDict["libraries"].pop(lib) + [wentWell, _] = writeProjectFile(projectDict) + return (wentWell, "") + return (False, "Library with this name is not declared") + + +def addLibraryInProject(lib: str, relPath: str, std: str) -> "tuple[bool, str]": + (exists, projectDict) = getProjectDict() + if not exists: + return (False, "Project doesn't exist.") + if "libraries" not in projectDict.keys(): + projectDict["libraries"] = {} + if lib not in projectDict["libraries"].keys(): + libDir = getRelativePathToRoot(os.path.join(os.getcwd(), relPath)) + projectDict["libraries"][lib] = {} + projectDict["libraries"][lib]["vhdl-version"] = std + projectDict["libraries"][lib]["path"] = libDir + os.makedirs(name=relPath,exist_ok=True) + print(libDir) + [wentWell, _] = writeProjectFile(projectDict) + return (wentWell, "") + return (False, "Library with this name is already declared") + + +def getLibrariesInProject() -> "tuple[bool, dict[str, Any]]": + (exists, projectDict) = getProjectDict() + if not exists: + return (False, {}) + libs = {} + if "libraries" not in projectDict.keys(): + ## Successful read, no libs found -> empty return + return (True, {}) + (_, projectRoot) = findProjectRoot() + for lib in projectDict["libraries"].keys(): + libs[lib] = projectDict["libraries"][lib] + absPath = os.path.join(projectRoot, libs[lib]["path"]) + libs[lib]["path"] = os.path.relpath(absPath, os.getcwd()) + return (True, libs) + +def getLibrariesPresent() -> "tuple[bool, dict[str, Any]]": + cwd = os.getcwd() + (exists, libs) = getLibrariesInProject() + if not exists: + return (False, {}) + libsInCwd = {} + for lib in libs.keys(): + if os.path.samefile(libs[lib]["path"], cwd): + libsInCwd[lib] = libs[lib] + return (True, libsInCwd) + +def createProjectFileTemplate(projectName: str) -> str: + return f""" +title = "{projectName}" +createdAt = "{datetime.date.today()}" +maintainer = "" +email = "" +version = "0.0.1" +""" +