Skip to content

Commit

Permalink
shorthand clock specification and other fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
kammoh committed May 5, 2024
1 parent 6489e31 commit 98f433e
Show file tree
Hide file tree
Showing 7 changed files with 325 additions and 96 deletions.
99 changes: 99 additions & 0 deletions get_oss_cad_suite.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#!/usr/bin/env python3

# pip install progressbar
import progressbar

import json
import pprint
import urllib.request
import platform
from pathlib import Path
from shutil import unpack_archive, rmtree

system = platform.system().lower()
arch = platform.machine().lower()

if arch == "x86_64" or arch == "amd64":
arch = "x64"
elif arch == "aarch64":
arch = "arm64"

# print("platform=", platform.platform())
print(system, arch)

repo = "YosysHQ/oss-cad-suite-build"

_json = json.loads(
urllib.request.urlopen(
urllib.request.Request(
f"https://api.github.com/repos/{repo}/releases/latest",
headers={"Accept": "application/vnd.github.v3+json"},
)
).read()
)

assets = _json["assets"]

asset_ext = "tgz" if system in ["linux", "darwin"] else "zip"

assets = [
asset
for asset in assets
if asset["name"].endswith(asset_ext) and f"{system}-{arch}" in asset["name"]
]

if not assets:
print(f"No suitable asset found for {system}-{arch}.")
exit(1)

asset = assets[0]
# pprint.pprint(asset)
name = asset["name"]
size = asset["size"]
updated_at = asset["updated_at"]
url = asset["browser_download_url"]


class MyProgressBar:
def __init__(self):
self.pbar = None

def __call__(self, block_num, block_size, total_size):
if not self.pbar:
self.pbar = progressbar.ProgressBar(maxval=total_size)
self.pbar.start()

downloaded = block_num * block_size
if downloaded < total_size:
self.pbar.update(downloaded)
else:
self.pbar.finish()


archive_file = Path(name)
if archive_file.exists() and archive_file.stat().st_size == size:
print(f"using previously downloaded {archive_file}...")
else:
print(f"Downloading {name} ({size} Bytes) from {url}...")
urllib.request.urlretrieve(url, name, MyProgressBar())


target_dir = Path.home() / ".xeda" / "tools"

# content of the archive
target_subdir = target_dir / "oss-cad-suite"

if target_dir.exists():
if target_subdir.exists():
print(f"Removing existing installation at {target_subdir}...")
rmtree(target_subdir)
else:
target_dir.mkdir(parents=True)

print(f"Unpacking {name} to {target_subdir}...")
unpack_archive(name, target_dir)

assert target_subdir.exists() and target_subdir.is_dir()
bin_dir = target_subdir / "bin"
assert bin_dir.exists() and bin_dir.is_dir()
print(f"Installation complete. Add {bin_dir} to your PATH.")
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ dependencies = [
"pyvcd ~= 0.3",
"GitPython >= 3.1.30",
"fabric >= 3.1.0",
"execnet >= 2.0.2",
"execnet >= 2.1.1",
"devtools >= 0.11",
]

Expand Down
46 changes: 33 additions & 13 deletions src/xeda/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,11 @@ def setup_logger(log_level, detailed_logs, log_to_file: Optional[Path] = None):
logging.getLogger().setLevel(log_level)
coloredlogs.install(
level=log_level,
fmt="[%(name)s] %(asctime)s %(levelname)s %(message)s"
if detailed_logs
else "%(levelname)s %(message)s",
fmt=(
"[%(name)s] %(asctime)s %(levelname)s %(message)s"
if detailed_logs
else "%(levelname)s %(message)s"
),
logger=log.root,
)
if detailed_logs:
Expand Down Expand Up @@ -107,6 +109,18 @@ def cli(ctx: click.Context, **kwargs):
metavar="FLOW_NAME",
type=click.Choice(all_flow_names),
)
@click.argument(
"design_file",
metavar="DESIGN",
type=click.Path(
file_okay=True,
dir_okay=False,
readable=True,
path_type=Path,
),
default=None,
required=False,
)
@click.option(
"--xeda-run-dir",
type=click.Path(
Expand Down Expand Up @@ -154,12 +168,6 @@ def cli(ctx: click.Context, **kwargs):
),
help="Path to Xeda project file.",
)
@click.option(
"--design-name",
# cls=ClickMutex,
# mutually_exclusive_with=["design_file"],
help="Specify design.name in case multiple designs are available in a xedaproject.",
)
@click.option(
"--design",
"--design-file",
Expand All @@ -177,6 +185,12 @@ def cli(ctx: click.Context, **kwargs):
mutually_exclusive_with=["design_name"],
help="Path to Xeda design file containing the description of a single design.",
)
@click.option(
"--design-name",
cls=ClickMutex,
mutually_exclusive_with=["design_file"],
help="Specify design.name in case multiple designs are available in a xedaproject.",
)
@click.option(
"--design-overrides",
metavar="KEY=VALUE...",
Expand All @@ -201,7 +215,7 @@ def cli(ctx: click.Context, **kwargs):
default=tuple(),
help="""Override setting values for the executed flow. Separate multiple KEY=VALUE overrides with commas. KEY can be a hierarchical name using dot notation.
Example: --settings clock_period=2.345 impl.strategy=Debug
"""
""",
# examples: # FIXME move to docs
# - xeda vivado_sim --flow-settings stop_time=100us
# - xeda vivado_synth --flow-settings impl.strategy=Debug --flow-settings clock_period=2.345
Expand Down Expand Up @@ -233,8 +247,9 @@ def cli(ctx: click.Context, **kwargs):
def run(
ctx: click.Context,
flow: str,
cached_dependencies: bool,
flow_settings: Union[None, str, Iterable[str]],
design_file: Optional[str] = None,
cached_dependencies: bool = True,
flow_settings: Union[None, str, Iterable[str]] = None,
incremental: bool = True,
clean: bool = False,
xeda_run_dir: Optional[Path] = None,
Expand Down Expand Up @@ -268,6 +283,11 @@ def run(
else:
flow_settings = []

if not design and design_file:
design = design_file
if not design:
sys.exit("No design file specified!")

if remote:
from .flow_runner.remote import RemoteRunner

Expand Down Expand Up @@ -674,7 +694,7 @@ def completion(_ctx: click.Context, stdout, shell=None):
completion_class = get_completion_class(shell)
if completion_class:
complete = completion_class(
cli=cli, ctx_args={}, prog_name=__package__, complete_var="source_xeda"
cli=cli, ctx_args={}, prog_name=__package__ or "xeda", complete_var="source_xeda"
)
print(complete.source())
else:
Expand Down
87 changes: 64 additions & 23 deletions src/xeda/design.py
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,11 @@ def src_with_type(src, stc_type):

class Clock(XedaBaseModel):
port: str
name: Optional[str] = None

@validator("name", pre=True, always=True)
def _name_validate(cls, value, values) -> str:
return value or values.get("port", None)


class Generator(XedaBaseModel):
Expand Down Expand Up @@ -669,22 +674,40 @@ def _authors_from_str(cls, value):
return [value]
return value

def __init__(
self,
design_root: Union[None, str, os.PathLike] = None,
**data: Any,
) -> None:
@classmethod
def process_compatibility(cls, data: Dict[str, Any]) -> Dict[str, Any]:
if "rtl" not in data:
clocks = data.pop("clocks", None)
if clocks is None:
clock = data.pop("clock", None)
if clock:
clocks = list(clock) if isinstance(clock, (list, tuple)) else [clock]
if (
clocks
and isinstance(clocks, (list, tuple))
and all(isinstance(c, str) for c in clocks)
):
clocks = {c: {"port": c} for c in clocks}
data["rtl"] = {
"generator": data.pop("generator", None),
"sources": data.pop("sources", []),
"parameters": data.pop("parameters", []),
"defines": data.pop("defines", []),
"top": data.pop("top", None),
"clocks": clocks,
}
return data

@classmethod
def process_generation(cls, data: Dict[str, Any]):
design_root = data.get("design_root")
if not design_root:
design_root = data.pop("design_root", Path.cwd())
assert design_root
if not isinstance(design_root, Path):
design_root = Path.cwd()
else:
design_root = Path(design_root)
design_root = design_root.resolve()
if not data.get("design_root"):
data["design_root"] = design_root
with WorkingDirectory(design_root):
generator = data.get("rtl", {}).get("generator", None)
if generator:
generator = data.get("rtl", {}).get("generator", None)
if generator:
with WorkingDirectory(design_root):
log.info("Running generator: %s", generator)
if isinstance(generator, str):
os.system(generator) # nosec S605
Expand All @@ -709,6 +732,28 @@ def __init__(
# raise FileNotFoundError(gen_script)
# args.insert(0, sys.executable)
subprocess.run(args, check=True, cwd=design_root)

@classmethod
def process_dict(cls, data: Dict[str, Any]) -> Dict[str, Any]:
data = cls.process_compatibility(data)
cls.process_generation(data)
return data

def __init__(
self,
design_root: Union[None, str, os.PathLike] = None,
**data: Any,
) -> None:
if not design_root:
design_root = data.pop("design_root", Path.cwd())
assert design_root
if not isinstance(design_root, Path):
design_root = Path(design_root)
design_root = design_root.resolve()
if not data.get("design_root"):
data["design_root"] = design_root
data = Design.process_dict(data)
with WorkingDirectory(design_root):
try:
super().__init__(**data)
except ValidationError as e:
Expand Down Expand Up @@ -784,13 +829,6 @@ def from_toml(
allow_extra: bool = False,
remove_extra: Optional[List[str]] = None,
) -> DesignType:
if overrides is None:
overrides = {}
if remove_extra is None:
remove_extra = []
if not isinstance(design_file, Path):
design_file = Path(design_file)
assert design_file.suffix == ".toml"
return cls.from_file(
design_file,
design_root=design_root,
Expand Down Expand Up @@ -827,7 +865,7 @@ def from_file(
if "name" not in design_dict:
design_name = design_file.stem
design_name = removesuffix(design_name, ".xeda")
log.warning(
log.debug(
"'design.name' not specified! Inferring design name: `%s` from design file name.",
design_name,
)
Expand Down Expand Up @@ -889,10 +927,13 @@ def rtl_hash(self) -> str:
@cached_property
def tb_hash(self) -> str:
hashes = list(sorted(src.content_hash for src in self.tb.sources))
param_strs = [f"{p}={v}" for p, v in self.tb.parameters.items()]
param_strs = [
f"{p}={v}" for p, v in self.tb.parameters.items() # pylint: disable=no-member
]
r = bytes(", ".join(hashes + param_strs), "utf-8")
return hashlib.sha3_256(r).hexdigest()

# pylint: disable=arguments-differ
def dict(self):
return super().dict(
exclude_unset=True,
Expand Down
Loading

0 comments on commit 98f433e

Please sign in to comment.