Skip to content

Commit

Permalink
Fix backward compatibility to old project structure and some housekee…
Browse files Browse the repository at this point in the history
…ping (#233)

* Fabulous: Fix backward compatibility to old project structure

Add case for old tcl scripts in run_FABulous_bitstream.

ConfigMemPath in fabric_gen:generateBitstreamSpec was set wrong, if a project used the old fabric.csv structure.

Signed-off-by: Jonas K. <[email protected]>

* FABulous: Remove unneeded get_path() function and use Path() instead of pathlib.Path()

Signed-off-by: Jonas K. <[email protected]>

* FABulous: Add check if external programs are installed

Signed-off-by: Jonas K. <[email protected]>

---------

Signed-off-by: Jonas K. <[email protected]>
  • Loading branch information
EverythingElseWasAlreadyTaken authored Sep 25, 2024
1 parent 14c1285 commit e2f2e85
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 67 deletions.
105 changes: 65 additions & 40 deletions FABulous/FABulous.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
import traceback
from contextlib import redirect_stdout
from glob import glob
from pathlib import PurePosixPath, PureWindowsPath, Path
from pathlib import PurePath, Path
from typing import List, Literal
from dotenv import load_dotenv

Expand Down Expand Up @@ -252,6 +252,37 @@ def make_hex(binfile, outfile):
print("0", file=f)


def check_if_application_exists(application: str, throw_exception: bool = True) -> Path:
"""Checks if an application is installed on the system.
Parameters
----------
application : str
Name of the application to check.
throw_exception : bool, optional
If True, throws an exception if the application is not installed, by default True
Returns
-------
Path
Path to the application, if installed.
Raises
------
Exception
If the application is not installed and throw_exception is True.
"""
path = shutil.which(application)
if path is not None:
return Path(path)
else:
logger.error(
f"{application} is not installed. Please install it or set FAB_<APPLICATION>_PATH in the .env file."
)
if throw_exception:
raise Exception(f"{application} is not installed.")


def adjust_directory_in_verilog_tb(project_dir):
"""Adjusts directory paths in a Verilog testbench file by replacing
the string "PROJECT_DIR" in the project_template with the actual
Expand All @@ -271,35 +302,6 @@ def adjust_directory_in_verilog_tb(project_dir):
fout.write(line.replace("PROJECT_DIR", f"{project_dir}"))


def get_path(path):
"""Returns system-specific path object.
Parameters
----------
path : str
File system path.
Returns
-------
PurePath
System-specific path object (PurePosixPath or PureWindowsPath)
Raises
------
NotImplementedError
If the operating system is not supported.
"""
system = platform.system()
# Darwin corresponds to MacOS, which also uses POSIX-style paths
if system == "Linux" or system == "Darwin":
return PurePosixPath(path)
elif system == "Windows":
return PureWindowsPath(path)
else:
logger.error(f"Unsupported operating system: {system}")
raise NotImplementedError


class PlaceAndRouteError(Exception):
"""An exception to be thrown when place and route fails."""

Expand Down Expand Up @@ -1168,7 +1170,7 @@ def do_synthesis(self, args):
f"do_synthesis takes exactly one argument ({len(args)} given)"
)
logger.info(f"Running synthesis that targeting Nextpnr with design {args[0]}")
path = get_path(args[0])
path = PurePath(args[0])
parent = path.parent
verilog_file = path.name
top_module_name = path.stem
Expand All @@ -1182,8 +1184,9 @@ def do_synthesis(self, args):
return

json_file = top_module_name + ".json"
yosys = check_if_application_exists(os.getenv("FAB_YOSYS_PATH", "yosys"))
runCmd = [
f"{os.getenv('FAB_YOSYS_PATH', 'yosys')}",
f"{yosys}",
"-p",
f"synth_fabulous -top top_wrapper -json {self.projectDir}/{parent}/{json_file}",
f"{self.projectDir}/{parent}/{verilog_file}",
Expand Down Expand Up @@ -1231,7 +1234,7 @@ def do_place_and_route(self, args):
f"do_place_and_route takes exactly one argument ({len(args)} given)"
)
logger.info(f"Running Placement and Routing with Nextpnr for design {args[0]}")
path = get_path(args[0])
path = PurePath(args[0])
parent = path.parent
json_file = path.name
top_module_name = path.stem
Expand Down Expand Up @@ -1261,10 +1264,13 @@ def do_place_and_route(self, args):

if os.path.exists(f"{self.projectDir}/{parent}"):
# TODO rewriting the fab_arch script so no need to copy file for work around
npnr = check_if_application_exists(
os.getenv("FAB_NEXTPNR_PATH", "nextpnr-generic")
)
if f"{json_file}" in os.listdir(f"{self.projectDir}/{parent}"):
runCmd = [
f"FAB_ROOT={self.projectDir}",
f"{os.getenv('FAB_NEXTPNR_PATH', 'nextpnr-generic')}",
f"{npnr}",
"--uarch",
"fabulous",
"--json",
Expand Down Expand Up @@ -1321,7 +1327,7 @@ def do_gen_bitStream_binary(self, args):
if len(args) != 1:
logger.error("Usage: gen_bitStream_binary <fasm_file>")
return
path = get_path(args[0])
path = PurePath(args[0])
parent = path.parent
fasm_file = path.name
top_module_name = path.stem
Expand Down Expand Up @@ -1454,9 +1460,12 @@ def do_run_simulation(self, args):
os.path.join(tmp_dir, filename) for filename in os.listdir(tmp_dir)
]

iverilog = check_if_application_exists(
os.getenv("FAB_IVERILOG_PATH", "iverilog")
)
try:
runCmd = [
f"os.getenv('FAB_IVERILOG_PATH', 'iverilog')",
f"{iverilog}",
"-D",
f"{defined_option}",
"-s",
Expand All @@ -1479,9 +1488,10 @@ def do_run_simulation(self, args):
f"{self.projectDir}/{path}/{bitstream_hex}",
)

vvp = check_if_application_exists(os.getenv("FAB_VVP_PATH", "vvp"))
try:
runCmd = [
f"{os.getenv('FAB_VVP_PATH', 'vvp')}",
f"{vvp}",
f"{self.projectDir}/{path}/{vvp_file}",
]
sp.run(runCmd, check=True)
Expand Down Expand Up @@ -1509,11 +1519,26 @@ def do_run_FABulous_bitstream(self, *args):
run_FABulous_bitstream <top_module_file>
"""
if len(args) != 1:
if len(args) == 1:
verilog_file_path = PurePath(args[0])
elif len(args) == 2:
# Backwards compatibility to older scripts
if "npnr" in args[0]:
verilog_file_path = PurePath(args[1])
elif "vpr" in args[0]:
logger.error(
"run_FABulous_bitstream does not support vpr anymore, please use npnr or try an older FABulous version."
)
return

else:
logger.error(f"run_FABulous_bitstream does not support {args[0]}")
return

else:
logger.error("Usage: run_FABulous_bitstream <top_module_file>")
return

verilog_file_path = get_path(args[0])
file_path_no_suffix = verilog_file_path.parent / verilog_file_path.stem

if verilog_file_path.suffix != ".v":
Expand Down Expand Up @@ -1554,7 +1579,7 @@ def do_tcl(self, args):
logger.error("Usage: tcl <tcl_script>")
return
path_str = args[0]
path = get_path(path_str)
path = PurePath(path_str)
name = path.stem
if not os.path.exists(path_str):
logger.error(f"Cannot find {path_str}")
Expand Down
28 changes: 21 additions & 7 deletions FABulous/fabric_generator/fabric_gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import csv
import math
import os
import pathlib
import re
import string
from sys import prefix
Expand Down Expand Up @@ -69,7 +68,7 @@ def __init__(self, fabric: Fabric, writer: codeGenerator) -> None:
self.writer = writer

@staticmethod
def bootstrapSwitchMatrix(tile: Tile, outputDir: pathlib.Path) -> None:
def bootstrapSwitchMatrix(tile: Tile, outputDir: Path) -> None:
"""Generates a blank switch matrix CSV file for the given tile. The top left corner will
contain the name of the tile. Columns are the source signals and rows are the destination signals.
Expand Down Expand Up @@ -117,7 +116,7 @@ def bootstrapSwitchMatrix(tile: Tile, outputDir: pathlib.Path) -> None:
writer.writerow([p] + [0] * len(destName))

@staticmethod
def list2CSV(InFileName: pathlib.Path, OutFileName: pathlib.Path) -> None:
def list2CSV(InFileName: Path, OutFileName: Path) -> None:
"""This function is used to export a given list description into its equivalent CSV switch matrix description.
A comment will be appended to the end of the column and row of the matrix, which will indicate the number
of signals in a given row.
Expand Down Expand Up @@ -2425,10 +2424,21 @@ def generateBitsStreamSpec(self) -> Dict[str, Dict]:
for x, tile in enumerate(row):
if tile == None:
continue
configMemPath = tile.tileDir.parent.joinpath(
f"{tile.name}_ConfigMem.csv"
)
if configMemPath.exists():
if "fabric.csv" in str(tile.tileDir):
# backward compatibility for old project structure
configMemPath = (
Path(os.getenv("FAB_PROJ_DIR"))
/ "Tile"
/ tile.name
/ f"{tile.name}_ConfigMem.csv"
)
else:
configMemPath = tile.tileDir.parent.joinpath(
f"{tile.name}_ConfigMem.csv"
)
logger.info(f"ConfigMemPath: {configMemPath}")

if configMemPath.exists() and configMemPath.is_file():
configMemList = parseConfigMem(
configMemPath,
self.fabric.maxFramesPerCol,
Expand All @@ -2439,6 +2449,10 @@ def generateBitsStreamSpec(self) -> Dict[str, Dict]:
logger.critical(
f"No ConfigMem csv file found for {tile.name} which have config bits"
)
configMemList = []
else:
logger.info(f"No config memory for {tile.name}.")
configMemList = []

encodeDict = [-1] * (
self.fabric.maxFramesPerCol * self.fabric.frameBitsPerRow
Expand Down
Loading

0 comments on commit e2f2e85

Please sign in to comment.