Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions flow/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -724,7 +724,7 @@ clean_abstract:
check-klayout:
@if [ -z "$(KLAYOUT_CMD)" ]; then \
echo "Error: KLayout not found. Install KLayout or set KLAYOUT_CMD."; \
echo "Hint: KLayout is needed for GDS/DRC/LVS targets."; \
echo "Hint: KLayout is needed for DRC, LVS, the gallery and the viewers."; \
exit 1; \
fi

Expand All @@ -747,7 +747,7 @@ $(WRAPPED_GDSOAS): $(OBJECTS_DIR)/klayout_wrap.lyt $(WRAPPED_LEFS)

# Merge GDS using Klayout
#-------------------------------------------------------------------------------
$(GDS_MERGED_FILE): $(RESULTS_DIR)/6_final.def $(OBJECTS_DIR)/klayout.lyt $(GDSOAS_FILES) $(WRAPPED_GDSOAS) $(SEAL_GDSOAS) | check-klayout
$(GDS_MERGED_FILE): $(RESULTS_DIR)/6_final.def $(OBJECTS_DIR)/klayout.lyt $(GDSOAS_FILES) $(WRAPPED_GDSOAS) $(SEAL_GDSOAS)
$(UNSET_AND_MAKE) do-gds-merged

.PHONY: do-gds-merged
Expand Down Expand Up @@ -775,7 +775,7 @@ $(GDS_FINAL_FILE): $(GDS_MERGED_FILE)
.PHONY: drc
drc: $(REPORTS_DIR)/6_drc.lyrdb

$(REPORTS_DIR)/6_drc.lyrdb: $(GDS_FINAL_FILE) $(KLAYOUT_DRC_FILE)
$(REPORTS_DIR)/6_drc.lyrdb: $(GDS_FINAL_FILE) $(KLAYOUT_DRC_FILE) | check-klayout
ifneq ($(KLAYOUT_DRC_FILE),)
$(RUN_CMD) --log $(abspath $(LOG_DIR)/6_drc.log) --tee -- \
$(SCRIPTS_DIR)/klayout.sh -zz -rd in_gds="$<" \
Expand All @@ -796,7 +796,7 @@ $(OBJECTS_DIR)/6_final_concat.cdl: $(RESULTS_DIR)/6_final.cdl $(CDL_FILE)
.PHONY: lvs
lvs: $(RESULTS_DIR)/6_lvs.lvsdb

$(RESULTS_DIR)/6_lvs.lvsdb: $(GDS_FINAL_FILE) $(KLAYOUT_LVS_FILE) $(OBJECTS_DIR)/6_final_concat.cdl
$(RESULTS_DIR)/6_lvs.lvsdb: $(GDS_FINAL_FILE) $(KLAYOUT_LVS_FILE) $(OBJECTS_DIR)/6_final_concat.cdl | check-klayout
ifneq ($(KLAYOUT_LVS_FILE),)
$(RUN_CMD) --log $(abspath $(LOG_DIR)/6_lvs.log) --tee -- \
$(SCRIPTS_DIR)/klayout.sh -b -rd in_gds="$<" \
Expand Down
26 changes: 20 additions & 6 deletions flow/docs/KLayoutOptionalDependency.md
Original file line number Diff line number Diff line change
@@ -1,36 +1,50 @@
# KLayout as an Optional Dependency

KLayout is only required for GDS/OAS stream generation, DRC, and LVS
KLayout is only required for a real GDS/OAS stream, DRC, and LVS
verification. All other ORFS functionality — synthesis, floorplanning,
placement, CTS, routing, timing reports, and abstract generation — works
without KLayout installed.

KLayout is a big application, packaged differently on every
distribution, and writing out a GDS file is a small task at the end of
the flow. Many who run ORFS don't need or want a GDS file. Without
KLayout, `make` therefore streams out an empty file and warns:

```
WARNING: klayout not found, mocking klayout by writing empty file ./results/asap7/gcd/base/6_1_merged.gds
```

Nobody mistakes an empty GDS file for a valid one. Installing KLayout
opts out: the GDS file is then written by KLayout. A GDS file written
empty before KLayout was installed is up to date as far as make knows;
`make clean_finish` removes it.

## Makefile Targets

| Target | Requires KLayout | Description |
|---|---|---|
| `make finish` | Yes | Complete flow including GDS generation |
| `make gds` | Yes | Generate GDS/OAS from finished design |
| `make finish` | No (empty GDS without it) | Complete flow including GDS generation |
| `make gds` | No (empty GDS without it) | Generate GDS/OAS from finished design |
| `make drc` | Yes | Run DRC checks (requires GDS) |
| `make lvs` | Yes | Run LVS checks (requires GDS) |
| `make gallery` | Yes | Generate layout screenshots |
| `make klayout_<file>` | Yes | Open result in KLayout viewer |
| `make generate_abstract` | No | Generate LEF/LIB abstracts |

A `check-klayout` guard produces a clear error message when KLayout is
missing and a KLayout-dependent target is invoked:
missing and a target that needs it is invoked:

```
Error: KLayout not found. Install KLayout or set KLAYOUT_CMD.
Hint: KLayout is needed for GDS/DRC/LVS targets.
Hint: KLayout is needed for DRC, LVS, the gallery and the viewers.
```

## bazel-orfs Integration

bazel-orfs uses the `do-` prefixed targets which bypass Make's dependency
management. `do-finish` / `do-final` only run the finish stage recipe
itself, while `make finish` also pulls in the GDS target as a Make
dependency. `do-gds` runs GDS generation separately (requires KLayout).
dependency. `do-gds` runs GDS generation separately (an empty file without KLayout).

An `orfs_gds()` Bazel rule can call `do-gds` independently from
`orfs_flow()`, making KLayout an optional toolchain dependency configured
Expand Down
21 changes: 21 additions & 0 deletions flow/scripts/klayout.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,27 @@
#!/usr/bin/env bash
set -u -eo pipefail
if [ -z "$KLAYOUT_CMD" ]; then
# Quality of life for those who don't need or want a GDS file: KLayout
# is a big, distribution-specific dependency for the small task of
# writing one out. Without it, streaming out GDS/OAS writes an empty
# file and warns, so the flow runs to the end. Installing KLayout opts
# out. Nobody mistakes an empty GDS file for a valid one; DRC, LVS and
# the viewers still need KLayout and stop here.
out_file=""
stream=0
prev=""
for arg in "$@"; do
case "$prev,$arg" in
-rd,out_file=*) out_file="${arg#out_file=}" ;;
-r,*/def2stream.py) stream=1 ;;
Comment thread
oharboe marked this conversation as resolved.
Resolved
esac
prev="$arg"
done
if [ "$stream" = 1 ] && [ -n "$out_file" ]; then
echo "WARNING: klayout not found, mocking klayout by writing empty file $out_file"
: >"$out_file"
exit 0
fi
Comment thread
oharboe marked this conversation as resolved.
Resolved
echo "Error: KLayout not found in PATH"
exit 1
fi
Expand Down
83 changes: 83 additions & 0 deletions flow/test/test_klayout_sh.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#!/usr/bin/env python3
"""klayout.sh without KLayout: streaming out GDS writes an empty file and
warns, so the flow runs to the end for those who don't need or want a GDS
file. Every other KLayout call, DRC and LVS among them, still stops with
an error. With KLayout, the call goes to it unchanged.
"""

import os
import subprocess
import tempfile
import unittest

SCRIPTS_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "scripts")
KLAYOUT_SH = os.path.join(SCRIPTS_DIR, "klayout.sh")


class TestKlayoutSh(unittest.TestCase):
def setUp(self):
self.tmp_dir = tempfile.TemporaryDirectory()
self.out_file = os.path.join(self.tmp_dir.name, "6_1_merged.gds")

def tearDown(self):
self.tmp_dir.cleanup()

def run_klayout(self, klayout_cmd, *args):
return subprocess.run(
["bash", KLAYOUT_SH, *args],
env=dict(os.environ, KLAYOUT_CMD=klayout_cmd),
capture_output=True,
text=True,
)

def stream_args(self):
return [
"-zz",
"-rd",
"design_name=gcd",
"-rd",
f"out_file={self.out_file}",
"-r",
"/flow/util/def2stream.py",
]

def test_stream_out_writes_an_empty_file_and_warns(self):
result = self.run_klayout("", *self.stream_args())
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
self.assertIn(
"WARNING: klayout not found, mocking klayout by writing empty file "
+ self.out_file,
result.stdout,
)
self.assertEqual(os.path.getsize(self.out_file), 0)

def test_drc_still_needs_klayout(self):
result = self.run_klayout(
"",
"-zz",
"-rd",
"in_gds=6_final.gds",
"-rd",
"report_file=6_drc.lyrdb",
"-r",
"/flow/platforms/asap7/drc/asap7.lydrc",
)
self.assertEqual(result.returncode, 1)
self.assertIn("Error: KLayout not found", result.stdout)

def test_viewer_still_needs_klayout(self):
result = self.run_klayout("", "-nn", "klayout.lyt", "6_final.odb")
self.assertEqual(result.returncode, 1)
self.assertIn("Error: KLayout not found", result.stdout)
self.assertFalse(os.path.exists(self.out_file))

def test_installed_klayout_gets_the_call(self):
result = self.run_klayout("echo", *self.stream_args())
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
self.assertNotIn("WARNING", result.stdout)
self.assertIn(f"out_file={self.out_file}", result.stdout)
self.assertFalse(os.path.exists(self.out_file))


if __name__ == "__main__":
unittest.main()