-
Notifications
You must be signed in to change notification settings - Fork 60
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Sky130 - Convert to Pydantic, support sky130_scl #848
base: master
Are you sure you want to change the base?
Changes from 53 commits
52ee341
4206c3b
e59a9b5
87de6ef
d4916a3
73223f8
d58c30e
3e4ca48
43e2506
b4f020d
56ad76d
eb0bbc6
be66509
67b1786
0a61cd9
5571f59
2c18e6b
00a9bf7
3f3aa0f
8264cfa
cd53650
b6094f3
88ef8f3
ff937d6
1ba69b2
adcdfae
6554d91
1066c89
7e5adfe
f1c67c3
8500f3b
a9e4780
d19a3db
00e2a02
bb03824
0973afb
26fe2f0
8fa0f34
515a049
889dae6
89cec06
7f0f61c
b79fce7
7fe5b41
1328c82
2c77e95
6873b9c
c189a49
66bda46
675e7ed
ac9191b
ac5887f
dc34973
0a34662
f3a4bab
51de15a
bf04604
969b9d8
93bdd62
154f37d
852574c
a69a748
871a44f
ff7090c
f29444f
a41b44b
f936cfd
8b054b2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
name: End-to-end CAD flow smoketests | ||
|
||
on: | ||
push: | ||
branches: | ||
- main | ||
- test-ci | ||
- sky130_pydantic | ||
pull_request: | ||
|
||
jobs: | ||
sky130-cm-e2e: | ||
runs-on: [self-hosted] # TODO: replace with something centrally admined | ||
|
||
steps: | ||
# Clone this repo | ||
- name: Checkout | ||
uses: actions/[email protected] | ||
|
||
- name: Run syn (Genus), par (Innovus), drc (Pegasus), and lvs (Pegasus) on the GCD design in sky130, using sky130_scl. | ||
run: | | ||
cd e2e | ||
poetry install | ||
source $(poetry env info --path)/bin/activate | ||
# redirect all hammer output a temp file, since it contains proprietary tool logs | ||
tempfile=$(mktemp) | ||
|
||
# run cadence tools on design | ||
echo "log file is $tempfile on $(hostname)" | tee -a $tempfile | ||
echo "End-to-end CAD flow CI smoketest running on" ${{ github.head_ref }}.${{ github.sha }} > $tempfile 2>&1 | ||
make build >> $tempfile 2>&1 | ||
echo "running par.." | tee -a $tempfile | ||
make par >> $tempfile 2>&1 | ||
echo "running drc.." | tee -a $tempfile | ||
make drc >> $tempfile 2>&1 | ||
echo "running lvs.." | tee -a $tempfile | ||
make lvs >> $tempfile 2>&1 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not a huge fan of dumping something out to a file if it's sensitive. What happens if a current Hammer user accidentally removes one of these echos on accident and info is leaked? Maybe it's better to try to disable actions from logging entirely? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, I see your point but I don't see how that reasoning doesn't apply to any output masking/secret hiding in an action. I've added a warning comment to hopefully dissuade accidental leaking, I'm not really sure how to actually resolve this concern- maybe only allow maintainers to manually kick off the workflow once they've checked if no malicious changes to the actions? That seems like a single point of failure though |
||
|
||
# Verify DRC, LVS are clean (this is a very simple design, it should be clean without hacks) | ||
echo "Checking that LVS passed..." | tee -a $tempfile | ||
# TODO these are probably pretty brittle... | ||
if [[ $(grep "Run Result : MATCH" "$tempfile") ]]; then | ||
echo "LVS passed!" | tee -a $tempfile | ||
else | ||
echo "There's an LVS mismatch! see $tempfile for log" | tee -a $tempfile | ||
exit 1 | ||
fi | ||
|
||
echo "Checking that DRC is clean..." | tee -a $tempfile | ||
if [[ $(grep "Total DRC Results : 0 (0)" "$tempfile") ]]; then | ||
echo "DRC is clean!" | tee -a $tempfile | ||
else | ||
echo "There are DRC violations! see $tempfile for log" | tee -a $tempfile | ||
exit 1 | ||
fi |
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -322,6 +322,73 @@ def ensure_dirs_exist(self, path) -> None: | |
self.logger.info('Creating directory: {}'.format(dir_name)) | ||
os.makedirs(dir_name) | ||
|
||
def override_tech_libraries(self) -> None: | ||
""" | ||
Override library paths to cached or manually overriden tech collateral | ||
""" | ||
if not self.config.libraries: | ||
return | ||
|
||
tech_module: str = self.get_setting("vlsi.core.technology") | ||
tech_name = tech_module.split('.')[-1] | ||
manual_overrides = {} | ||
|
||
# if user has broken glass, map tech filename -> manual path | ||
if self.get_setting("vlsi.technology.manually_override_pdk_collateral"): | ||
for lib in self.get_setting("vlsi.technology.override_libraries"): | ||
for key, path in lib['library'].items(): | ||
fname = os.path.basename(path) | ||
if (key, fname) in manual_overrides: | ||
self.logger.error("Attempted to add {(key,path)} to overrides when {manual_overrides[(key, fname)]} already exists!") | ||
manual_overrides[(key, fname)] = path | ||
else: | ||
if len(self.get_setting("vlsi.technology.override_libraries")) > 0: | ||
self.logger.warning("You've attempted to specify override libraries without enabling vlsi.technology.manually_override_pdk_collateral! collateral paths will not be overwritten") | ||
|
||
used_overrides = [] | ||
for lib in self.config.libraries: | ||
for field_name in lib.model_fields: | ||
if field_name.endswith("_file") or field_name == "verilog_sim": | ||
# check if that file exists in cache, override if so | ||
default_path = getattr(lib, field_name) | ||
if not default_path: | ||
continue | ||
new_path = default_path | ||
fname = os.path.basename(default_path) | ||
fnames = [fname, fname.replace(".spice", ".cdl"), fname.replace(".cdl", ".spice")] | ||
|
||
cached_paths = set(os.path.join(self.cache_dir, f) for f in fnames) | ||
cached_paths = [f for f in cached_paths if os.path.exists(f)] | ||
|
||
# only allow 1 valid cached path, otherwise ambiguous | ||
if len(cached_paths) > 1: | ||
self.logger.error(f"ambiguous cache override: {cached_paths}") | ||
if cached_paths: | ||
used_overrides.append((field_name, fname)) | ||
new_path = cached_paths[0] | ||
self.logger.info( | ||
f"overriding {default_path} with cache entry {new_path}" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this kind of blows up the log since there are a bunch of instances of the same library... |
||
) | ||
|
||
manual_override_paths = set([f for f in fnames if (field_name, f) in manual_overrides]) | ||
manual_override_paths = [manual_overrides[(field_name, f)] for f in manual_override_paths] | ||
|
||
# only allow 1 valid manual_override path, otherwise ambiguous | ||
if len(manual_override_paths) > 1: | ||
self.logger.error(f"ambiguous cache override: {manual_override_paths}") | ||
# prioritize manual overrides over cache | ||
if manual_override_paths: | ||
used_overrides.append((field_name, fname)) | ||
new_path = manual_override_paths[0] | ||
self.logger.info( | ||
f"overriding {default_path} with manual override {new_path}" | ||
) | ||
|
||
setattr(lib, field_name, new_path) | ||
unused_overrides = [k for k in manual_overrides.keys() if k not in used_overrides] | ||
if unused_overrides: | ||
self.logger.warning(f"Unused tech collateral overrides: {unused_overrides}") | ||
|
||
# hammer-vlsi properties. | ||
# TODO: deduplicate/put these into an interface to share with HammerTool? | ||
@property | ||
|
@@ -353,8 +420,12 @@ def __init__(self): | |
self.time_unit: Optional[str] = None | ||
self.cap_unit: Optional[str] = None | ||
|
||
def gen_config(self) -> None: | ||
"""For subclasses to set self.config (type: TechJSON) directly, instead of from static JSON file""" | ||
pass | ||
|
||
@classmethod | ||
def load_from_module(cls, tech_module: str) -> Optional["HammerTechnology"]: | ||
def load_from_module(cls, tech_module: str) -> "HammerTechnology": | ||
"""Load a technology from a given module. | ||
|
||
:param tech_module: Technology module (e.g. "hammer.technology.asap7") | ||
|
@@ -375,8 +446,8 @@ def load_from_module(cls, tech_module: str) -> Optional["HammerTechnology"]: | |
elif tech_yaml.is_file(): | ||
tech.config = TechJSON.model_validate_json(json.dumps(load_yaml(tech_yaml.read_text()))) | ||
return tech | ||
else: #TODO - from Pydantic model instance | ||
return None | ||
else: # Assume tech implents gen_config() | ||
return tech | ||
|
||
def get_lib_units(self) -> None: | ||
""" | ||
|
@@ -415,6 +486,12 @@ def get_setting(self, key: str) -> Any: | |
print(e) # TODO: fix the root cause | ||
return None | ||
|
||
def set_setting(self, key: str, value: Any) -> None: | ||
""" | ||
Set a runtime setting in the database. | ||
""" | ||
self._database.set_setting(key, value) | ||
|
||
def get_setting_suffix(self, key: str) -> Any: | ||
"""Get a particular setting from the database with a suffix. | ||
""" | ||
|
@@ -648,15 +725,15 @@ def prepend_dir_path(self, path: str, lib: Optional[Library] = None) -> str: | |
|
||
1. Absolute path: the path starts with "/" and refers to an absolute path on the filesystem | ||
/path/to/a/lib/file.lib -> /path/to/a/lib/file.lib | ||
2. Tech plugin relative path: the path has no "/"s and refers to a file directly inside the tech plugin folder | ||
2. Tech plugin relative path: the path has no "/"s and refers to a file directly inside the tech plugin folder (no subdirectories allowed, else it conflicts with 3-5. below!) | ||
techlib.lib -> <tech plugin package>/techlib.lib | ||
3. Tech cache relative path: the path starts with an identifier which is "cache" (this is used in the SKY130 tech JSON) | ||
3. Tech cache relative path: the path starts with an identifier which is "cache" (this is used in the SKY130 Libraries) | ||
cache/primitives.v -> <tech plugin cache dir>/primitives.v | ||
4. Install relative path: the path starts with an install/tarball identifier (installs.id, tarballs.root.id) | ||
and refers to a file relative to that identifier's path | ||
pdkroot/dac/dac.lib -> /nfs/ecad/tsmc100/stdcells/dac/dac.lib | ||
5. Library extra_prefix path: the path starts with an identifier present in the provided | ||
library's extra_prefixes | ||
library's extra_prefixes Field | ||
lib1/cap150f.lib -> /design_files/caps/cap150f.lib | ||
""" | ||
assert len(path) > 0, "path must not be empty" | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally the CI is fully reproducible. I don't know whats in the
e2e
folder or whatpoetry
installs but in general I would make sure it's clear what tools are installed by default and what you are installing in the CI.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that's fair, we can remove the
poetry
call since this will only be run by one daemon on one machine, that is have it assume there's a poetry environment already set up- is that what you're asking? (It's unreasonable to have this run in isolation/install everything from scratch anyways since it relies on NFS paths for pdk collateral and tool binaries)