Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ jobs:
# run: uv run pyright chipcompiler

- name: Pytest
run: uv run --no-sync pytest test/ --ignore=test/test_harden.py --ignore=test/test_rcx.py --ignore=test/examples/test_soc.py --cov=chipcompiler --cov-report=
run: uv run --no-sync pytest test/ --ignore=test/integration/test_harden_flow.py --ignore=test/integration/test_rcx_flow.py --ignore=test/examples/test_soc.py --cov=chipcompiler --cov-report=

- name: Publish coverage summary
if: always()
Expand Down
4 changes: 2 additions & 2 deletions docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ uv run isort chipcompiler/ test/

```bash
uv run pytest test/
uv run pytest test/test_tools_yosys_utility.py -v
uv run pytest test/tools/yosys/test_utility.py -v
uv run pytest test/ --cov=chipcompiler --cov-report=term-missing
uv run pytest test/formal/ -v
```
Expand Down Expand Up @@ -349,7 +349,7 @@ For the ICS55 GCD tool integration test:
nix develop
export PATH=/path/to/ecc-sizer/build/src:$PATH
export CHIPCOMPILER_ICS55_PDK_ROOT=/path/to/ics55-pdk
.venv/bin/python -m pytest test/test_tools.py::test_ics55_gcd -q -s
.venv/bin/python -m pytest test/integration/test_rtl2gds_flow.py::test_ics55_gcd -q -s
```

### PDK
Expand Down
4 changes: 2 additions & 2 deletions docs/specification/filelist-grammar.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ any_char = ? any Unicode character ? ;

## Testing

See `test/test_filelist.py` for test coverage:
See `test/utility/filelist/` and `test/data/test_workspace_filelist.py` for test coverage:

- Basic filelist parsing
- `+incdir` directive parsing
Expand All @@ -88,4 +88,4 @@ See `test/test_filelist.py` for test coverage:

## References

- VCS User Guide - [User Guide](https://picture.iczhiku.com/resource/eetop/WhKDeOKWsJfQibVv.pdf)
- VCS User Guide - [User Guide](https://picture.iczhiku.com/resource/eetop/WhKDeOKWsJfQibVv.pdf)
7 changes: 6 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,13 @@ extend-include = ["*.spec"]
[tool.pytest.ini_options]
testpaths = ["test"]
norecursedirs = [".venv"]
markers = [
"integration: tests that run an ECC flow or large workspace orchestration",
"pdk: tests that require a complete external PDK installation",
"slow: tests that are expected to be expensive on a normal developer shell",
]
addopts = [
"--deselect=test/test_tools.py::test_sg13g2_gcd",
"--deselect=test/integration/test_rtl2gds_flow.py::test_sg13g2_gcd",
]

[tool.ty]
Expand Down
237 changes: 237 additions & 0 deletions test/cli/commands/test_check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
import json
import os

from chipcompiler.cli import main as cli_main


class TestCheck:
def test_check_passes_valid_config(self, tmp_path, monkeypatch, capsys, create_cli_project):
project_dir = create_cli_project()
monkeypatch.setattr(
"chipcompiler.cli.project.config._validate_pdk_contents",
lambda name, root: None,
)
rc = cli_main.run(["check", "--project", project_dir])
assert rc == 0
out = capsys.readouterr().out
assert "checked" in out

def test_check_from_inside_project_dir(self, tmp_path, monkeypatch, capsys, create_cli_project):
project_dir = create_cli_project()
monkeypatch.setattr(
"chipcompiler.cli.project.config._validate_pdk_contents",
lambda name, root: None,
)
monkeypatch.chdir(project_dir)
rc = cli_main.run(["check"])
assert rc == 0
out = capsys.readouterr().out
assert "checked" in out

def test_check_fails_missing_ecc_toml(self, tmp_path):
rc = cli_main.run(["check", "--project", str(tmp_path)])
assert rc == 1

def test_check_fails_malformed_toml(self, tmp_path, capsys):
project_dir = tmp_path / "bad"
project_dir.mkdir()
(project_dir / "ecc.toml").write_text("[design\ninvalid {{{")
rc = cli_main.run(["check", "--project", str(project_dir)])
assert rc == 1

def test_check_fails_missing_rtl(self, tmp_path, create_cli_project):
project_dir = create_cli_project()
toml_path = os.path.join(project_dir, "ecc.toml")
with open(toml_path, "w") as f:
f.write(
'[design]\nname="gcd"\ntop="gcd"\nrtl=["rtl/missing.v"]\n'
'clock_port="clk"\nfrequency_mhz=100\n'
'[pdk]\nname="ics55"\nroot=""\n'
'[flow]\npreset="rtl2gds"\nrun="default"\n',
)
rc = cli_main.run(["check", "--project", project_dir])
assert rc == 1

def test_check_fails_empty_pdk_root(self, tmp_path, create_cli_project):
project_dir = create_cli_project(pdk_root="")
rc = cli_main.run(["check", "--project", project_dir])
assert rc == 1

def test_check_fails_non_directory_pdk_root(self, tmp_path, create_cli_project):
pdk_root = tmp_path / "ics55.txt"
pdk_root.write_text("not a dir")
project_dir = create_cli_project(pdk_root=str(pdk_root))
rc = cli_main.run(["check", "--project", project_dir])
assert rc == 1

def test_check_fails_unsupported_pdk(self, tmp_path, create_cli_project):
project_dir = create_cli_project()
toml_path = os.path.join(project_dir, "ecc.toml")
with open(toml_path) as f:
content = f.read()
content = content.replace('name = "ics55"', 'name = "unsupported"')
with open(toml_path, "w") as f:
f.write(content)
rc = cli_main.run(["check", "--project", project_dir])
assert rc == 1

def test_check_fails_unsupported_preset(self, tmp_path, create_cli_project):
project_dir = create_cli_project()
toml_path = os.path.join(project_dir, "ecc.toml")
with open(toml_path) as f:
content = f.read()
content = content.replace('preset = "rtl2gds"', 'preset = "unknown"')
with open(toml_path, "w") as f:
f.write(content)
rc = cli_main.run(["check", "--project", project_dir])
assert rc == 1

def test_check_fails_non_positive_frequency(self, tmp_path, create_cli_project):
project_dir = create_cli_project()
toml_path = os.path.join(project_dir, "ecc.toml")
with open(toml_path) as f:
content = f.read()
content = content.replace("frequency_mhz = 100.0", "frequency_mhz = -10")
with open(toml_path, "w") as f:
f.write(content)
rc = cli_main.run(["check", "--project", project_dir])
assert rc == 1

def test_check_fails_multiple_rtl(self, tmp_path, create_cli_project):
project_dir = create_cli_project()
toml_path = os.path.join(project_dir, "ecc.toml")
with open(toml_path) as f:
content = f.read()
content = content.replace(
'rtl = ["rtl/gcd.v"]',
'rtl = ["rtl/a.v", "rtl/b.v"]',
)
with open(toml_path, "w") as f:
f.write(content)
rc = cli_main.run(["check", "--project", project_dir])
assert rc == 1

def test_check_fails_non_numeric_frequency(self, tmp_path, create_cli_project):
project_dir = create_cli_project()
toml_path = os.path.join(project_dir, "ecc.toml")
with open(toml_path) as f:
content = f.read()
content = content.replace("frequency_mhz = 100.0", 'frequency_mhz = "fast"')
with open(toml_path, "w") as f:
f.write(content)
rc = cli_main.run(["check", "--project", project_dir])
assert rc == 1

def test_check_json_output(self, tmp_path, monkeypatch, capsys, create_cli_project):
project_dir = create_cli_project()
monkeypatch.setattr(
"chipcompiler.cli.project.config._validate_pdk_contents",
lambda name, root: None,
)
rc = cli_main.run(["check", "--project", project_dir, "--json"])
assert rc == 0
out = capsys.readouterr().out
data = json.loads(out)
assert "records" in data
assert data["records"][0]["status"] == "checked"
assert data["records"][0]["project"] == "gcd"


class TestCheckFilelistValidation:
def test_check_fails_filelist_with_missing_sources(self, tmp_path, monkeypatch):
from chipcompiler.cli.project.config import _validate_pdk_contents

monkeypatch.setattr(
_validate_pdk_contents, "__wrapped__", lambda *a, **k: None, raising=False
)
monkeypatch.setattr(
"chipcompiler.cli.project.config._validate_pdk_contents", lambda *a, **k: None
)

project_dir = tmp_path / "flproj"
project_dir.mkdir()
(project_dir / "rtl").mkdir()
(project_dir / "rtl" / "gcd.v").write_text("module gcd; endmodule")

filelist = project_dir / "rtl" / "files.f"
filelist.write_text("gcd.v\nmissing.v\nother_missing.v\n")

pdk_root = tmp_path / "ics55"
pdk_root.mkdir()

toml = f'''[design]
name = "gcd"
top = "gcd"
rtl = ["rtl/files.f"]
clock_port = "clk"
frequency_mhz = 100.0

[pdk]
name = "ics55"
root = "{pdk_root}"

[flow]
preset = "rtl2gds"
run = "default"
'''
(project_dir / "ecc.toml").write_text(toml)
rc = cli_main.run(["check", "--project", str(project_dir)])
assert rc == 1

def test_check_fails_invalid_filelist_directive(self, tmp_path, monkeypatch):
monkeypatch.setattr(
"chipcompiler.cli.project.config._validate_pdk_contents", lambda *a, **k: None
)

project_dir = tmp_path / "flproj2"
project_dir.mkdir()
(project_dir / "rtl").mkdir()

filelist = project_dir / "rtl" / "files.f"
filelist.write_text("gcd.v\n-f other.f\n")

pdk_root = tmp_path / "ics55"
pdk_root.mkdir()

toml = f'''[design]
name = "gcd"
top = "gcd"
rtl = ["rtl/files.f"]
clock_port = "clk"
frequency_mhz = 100.0

[pdk]
name = "ics55"
root = "{pdk_root}"

[flow]
preset = "rtl2gds"
run = "default"
'''
(project_dir / "ecc.toml").write_text(toml)
rc = cli_main.run(["check", "--project", str(project_dir)])
assert rc == 1


class TestMissingConfigErrorRecord:
def test_check_missing_config_has_kind_error_json(self, tmp_path, capsys):
rc = cli_main.run(["check", "--project", str(tmp_path), "--json"])
assert rc == 1
data = json.loads(capsys.readouterr().out)
record = data["records"][0]
assert record["kind"] == "error"
assert record["error"] == "missing_config"

def test_check_missing_config_has_kind_error_text(self, tmp_path, capsys):
rc = cli_main.run(["check", "--project", str(tmp_path)])
assert rc == 1
out = capsys.readouterr().out
assert "[error]" in out
assert "missing_config" in out

def test_check_missing_config_has_disclosure_command(self, tmp_path, capsys):
rc = cli_main.run(["check", "--project", str(tmp_path), "--json"])
assert rc == 1
data = json.loads(capsys.readouterr().out)
record = data["records"][0]
assert "inspect" in record or "inspect_cmd" in record
96 changes: 96 additions & 0 deletions test/cli/commands/test_disclosure.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import json
import os

from chipcompiler.cli import main as cli_main


class TestDisclosureCommands:
def test_init_lines_have_disclosure(self, tmp_path, capsys, has_disclosure):
project_path = str(tmp_path / "disctest")
rc = cli_main.run(["init", project_path])
assert rc == 0
out = capsys.readouterr().out
assert has_disclosure(out)

def test_check_lines_have_disclosure(
self, tmp_path, monkeypatch, capsys, create_cli_project, has_disclosure
):
project_dir = create_cli_project()
monkeypatch.setattr(
"chipcompiler.cli.project.config._validate_pdk_contents",
lambda name, root: None,
)
rc = cli_main.run(["check", "--project", project_dir])
assert rc == 0
out = capsys.readouterr().out
assert has_disclosure(out)

def test_status_lines_have_disclosure(
self, tmp_path, capsys, create_cli_project, create_flow_json, has_disclosure
):
project_dir = create_cli_project()
run_dir = os.path.join(project_dir, "runs", "default")
create_flow_json(run_dir, profile="main")

rc = cli_main.run(["status", "--project", project_dir])
assert rc == 0
out = capsys.readouterr().out
assert has_disclosure(out)

def test_metrics_lines_have_disclosure(
self, tmp_path, capsys, create_cli_project, has_disclosure
):
project_dir = create_cli_project()
run_dir = os.path.join(project_dir, "runs", "default")

analysis_dir = os.path.join(run_dir, "Synthesis_yosys", "analysis")
os.makedirs(analysis_dir, exist_ok=True)
with open(os.path.join(analysis_dir, "Synthesis_metrics.json"), "w") as f:
json.dump({"Cell number": 312}, f)

rc = cli_main.run(["metrics", "synthesis", "--project", project_dir])
assert rc == 0
out = capsys.readouterr().out
assert has_disclosure(out)

def test_log_error_lines_have_disclosure(self, tmp_path, capsys, create_cli_project):
project_dir = create_cli_project()
run_dir = os.path.join(project_dir, "runs", "default")

step_dir = os.path.join(run_dir, "Synthesis_yosys", "log")
os.makedirs(step_dir, exist_ok=True)
with open(os.path.join(step_dir, "synthesis.log"), "w") as f:
f.write("Error: something failed\n")

rc = cli_main.run(["log", "synthesis", "--project", project_dir])
assert rc == 0
out = capsys.readouterr().out
assert "ecc log synthesis" in out

def test_project_arg_propagated_to_disclosure(
self, tmp_path, capsys, create_cli_project, create_flow_json
):
project_dir = create_cli_project()
run_dir = os.path.join(project_dir, "runs", "default")
create_flow_json(run_dir, profile="main")

rc = cli_main.run(["status", "--project", project_dir])
assert rc == 0
out = capsys.readouterr().out
assert f"--project {project_dir}" in out

def test_output_lowercase_tokens(self, tmp_path, capsys, create_cli_project, create_flow_json):
project_dir = create_cli_project()
run_dir = os.path.join(project_dir, "runs", "default")
create_flow_json(
run_dir,
[
{"name": "Synthesis", "tool": "yosys", "state": "Success", "runtime": "0:00:01"},
],
)

rc = cli_main.run(["status", "--project", project_dir])
assert rc == 0
out = capsys.readouterr().out
assert "synthesis" in out
assert "success" in out
Loading
Loading