diff --git a/.github/actions/build-pyinstaller-bundle/action.yml b/.github/actions/build-pyinstaller-bundle/action.yml index e2b873ca9..5ba0309ec 100644 --- a/.github/actions/build-pyinstaller-bundle/action.yml +++ b/.github/actions/build-pyinstaller-bundle/action.yml @@ -76,4 +76,6 @@ runs: "$SMOKE_DIR/ecc" --help "$SMOKE_DIR/ecc" --version "$SMOKE_DIR/ecc" version --json + "$SMOKE_DIR/ecc" doc config --plain > /dev/null + "$SMOKE_DIR/ecc" doc ug --lang cn --plain > /dev/null test -x "$SMOKE_DIR/_internal/torch/bin/torch_shm_manager" diff --git a/chipcompiler/cli/app.py b/chipcompiler/cli/app.py index 79a9b4e34..5dad46840 100644 --- a/chipcompiler/cli/app.py +++ b/chipcompiler/cli/app.py @@ -1,10 +1,11 @@ import json +import os from collections.abc import Sequence from typing import Annotated -import click import typer +from chipcompiler.cli.commands.doc import register_doc_commands from chipcompiler.cli.commands.doctor import register_doctor_commands from chipcompiler.cli.commands.param import param_app from chipcompiler.cli.commands.pdk import pdk_app @@ -14,20 +15,16 @@ from chipcompiler.cli.commands.rpc import rpc_app from chipcompiler.cli.commands.signoff import signoff_app from chipcompiler.cli.commands.workspace import workspace_app +from chipcompiler.cli.core.apps import create_app from chipcompiler.cli.core.version_info import root_version_line, version_payload, version_text from chipcompiler.cli.inspection.tool_versions import tool_versions -app = typer.Typer( - add_completion=False, - no_args_is_help=True, - rich_markup_mode=None, - help="ECC - EDA toolchain for RTL-to-GDS flows", -) +app = create_app(help="ECC - EDA toolchain for RTL-to-GDS flows", add_completion=True) def version_callback(value: bool) -> None: # noqa: FBT001 -- typer invokes Option callbacks positionally if value: - click.echo(root_version_line()) + typer.echo(root_version_line()) raise typer.Exit() @@ -57,17 +54,17 @@ def version_cmd( tools = tool_versions() if jsonl: for name in ("ecc", "dreamplace", "ecc_tools"): - click.echo(json.dumps({"component": name, "version": payload[name]})) + typer.echo(json.dumps({"component": name, "version": payload[name]})) for name, version in tools.items(): - click.echo(json.dumps({"component": name, "version": version})) + typer.echo(json.dumps({"component": name, "version": version})) elif json_output: - click.echo(json.dumps({**payload, "tools": tools})) + typer.echo(json.dumps({**payload, "tools": tools})) elif plain: from chipcompiler.cli.rendering.render import render_plain render_plain(({**payload, **tools},)) else: - click.echo(version_text(payload, tools)) + typer.echo(version_text(payload, tools)) @app.command("layout-image", help="Render a GDS file into a layout image") @@ -80,11 +77,13 @@ def layout_image_cmd( from chipcompiler.tools.klayout_tool.image import save_snapshot_image if not save_snapshot_image(gds_file=gds, img_file=image, width=width, height=height): - raise click.ClickException(f"Failed to render layout image from {gds} to {image}") + typer.echo(f"Error: Failed to render layout image from {gds} to {image}", err=True) + raise typer.Exit(1) register_project_commands(app) register_doctor_commands(app) +register_doc_commands(app) app.add_typer(param_app, name="param") app.add_typer(pdk_app, name="pdk") app.add_typer(project_app, name="project") @@ -95,21 +94,14 @@ def layout_image_cmd( def invoke_typer_app(argv: Sequence[str]) -> int: - if not argv: - command = typer.main.get_command(app) - click.echo(command.get_help(click.Context(command, info_name="ecc")), err=True) + command = typer.main.get_command(app) + # Shell completion requests carry their arguments in env vars, not argv. + if not argv and "_ECC_COMPLETE" not in os.environ: + typer.echo(command.get_help(typer.Context(command, info_name="ecc")), err=True) return 1 - command = typer.main.get_command(app) try: - result = command.main( - args=list(argv), - prog_name="ecc", - standalone_mode=False, - ) - except click.exceptions.Exit as exc: - return int(exc.exit_code or 0) - except click.ClickException as exc: - exc.show() - return int(exc.exit_code or 1) - return int(result or 0) + command.main(args=list(argv), prog_name="ecc") + except SystemExit as exc: + return int(exc.code or 0) + return 0 diff --git a/chipcompiler/cli/commands/doc.py b/chipcompiler/cli/commands/doc.py new file mode 100644 index 000000000..6fc6d1e97 --- /dev/null +++ b/chipcompiler/cli/commands/doc.py @@ -0,0 +1,77 @@ +"""Render the bundled CLI guide documents in the terminal.""" + +import sys +from enum import Enum +from typing import Annotated + +import typer + +from chipcompiler.cli.core import docs +from chipcompiler.cli.rendering.pretty import supports_color + + +class DocTopic(str, Enum): + config = "config" + ug = "ug" + tutorial = "tutorial" + dev = "dev" + + +class DocLanguage(str, Enum): + en = "en" + cn = "cn" + + +def register_doc_commands(app: typer.Typer) -> None: + app.command("doc", help="Show a bundled guide (config/ug/tutorial/dev) in the terminal")( + doc_cmd + ) + + +def doc_cmd( + topic: Annotated[DocTopic, typer.Argument(help="Guide to show")], + *, + lang: Annotated[DocLanguage, typer.Option("--lang", help="Guide language")] = DocLanguage.en, + plain: Annotated[ + bool, + typer.Option("--plain", help="Print the raw markdown instead of the rendered layout"), + ] = False, +) -> None: + try: + raw = docs.load_guide(topic.value, lang.value) + if plain: + _write_plain(raw) + return + text = raw.decode("utf-8") + except docs.GuideNotFoundError as exc: + typer.echo(f"Error: {exc}", err=True) + raise typer.Exit(1) from None + except UnicodeDecodeError: + typer.echo( + f"Error: guide is not valid UTF-8: {docs.GUIDE_STEMS[topic.value]}.{lang.value}.md", + err=True, + ) + raise typer.Exit(1) from None + except OSError as exc: + typer.echo(f"Error: could not read guide: {exc}", err=True) + raise typer.Exit(1) from None + + from chipcompiler.cli.rendering.render import render_markdown + + try: + render_markdown(text, color=supports_color(), pager=True) + except UnicodeEncodeError: + typer.echo( + "Error: terminal encoding cannot render this guide; try --plain", + err=True, + ) + raise typer.Exit(1) from None + + +def _write_plain(data: bytes) -> None: + stream = getattr(sys.stdout, "buffer", None) + if stream is None: + sys.stdout.write(data.decode("utf-8")) + else: + stream.write(data) + stream.flush() diff --git a/chipcompiler/cli/commands/param.py b/chipcompiler/cli/commands/param.py index 826953173..cfd014299 100644 --- a/chipcompiler/cli/commands/param.py +++ b/chipcompiler/cli/commands/param.py @@ -7,6 +7,7 @@ from chipcompiler.cli.command_handlers.param import param_set as param_set_handler from chipcompiler.cli.command_handlers.param import param_show as param_show_handler from chipcompiler.cli.command_handlers.param import param_unset as param_unset_handler +from chipcompiler.cli.core.apps import create_app from chipcompiler.cli.core.inputs import ( ParamDiffInput, ParamListInput, @@ -25,12 +26,7 @@ WorkspaceOption, ) -param_app = typer.Typer( - add_completion=False, - no_args_is_help=True, - rich_markup_mode=None, - help="Manage EDA parameters", -) +param_app = create_app(help="Manage EDA parameters") def _finish_param( @@ -41,7 +37,7 @@ def _finish_param( execute_command("param", command_input, handler, render_key=f"param:{param_command}") -@param_app.command("list", help="List parameter overrides") +@param_app.command("list") def list_cmd( *, project: ProjectOption = None, @@ -52,6 +48,20 @@ def list_cmd( jsonl: JsonlOption = False, plain: PlainOption = False, ) -> None: + """List parameter overrides. + + By default lists the legacy-semantic parameters (such as + `design.frequency_mhz`, `floorplan.core_util`, `cts.max_fanout`) plus + direct-config fields that already carry an override. Use `--step STEP` to + show the full reviewed schema of one step (field, type, and where each + value is written), or `--all` to show every reviewed field. + + ```bash + ecc param list --step cts + ``` + + See 'ecc doc config' for the full reference. + """ command_input = ParamListInput( output=output_options(json_output=json_output, jsonl=jsonl, plain=plain), project=project_options(project), @@ -62,7 +72,7 @@ def list_cmd( _finish_param("list", command_input, param_list_handler) -@param_app.command("show", help="Show one parameter value") +@param_app.command("show") def show_cmd( *, key: Annotated[str, typer.Argument()], @@ -72,6 +82,15 @@ def show_cmd( jsonl: JsonlOption = False, plain: PlainOption = False, ) -> None: + """Show one parameter value. + + Reports the current value, default, source, type, and allowed range, + plus the applicable write targets (`maps_to`, `config_target`, + `pdk_target`). Some parameters (such as `sta.max_paths`) are passed at + runtime instead of written to a generated configuration file. + + See 'ecc doc config' for the full reference. + """ command_input = ParamShowInput( output=output_options(json_output=json_output, jsonl=jsonl, plain=plain), project=project_options(project), @@ -81,11 +100,7 @@ def show_cmd( _finish_param("show", command_input, param_show_handler) -@param_app.command( - "set", - help="Set a parameter override", - context_settings={"ignore_unknown_options": True}, -) +@param_app.command("set", context_settings={"ignore_unknown_options": True}) def set_cmd( *, key: Annotated[str, typer.Argument()], @@ -96,6 +111,33 @@ def set_cmd( jsonl: JsonlOption = False, plain: PlainOption = False, ) -> None: + """Set a parameter override. + + Scopes: + + - project (default): the override is written to `ecc.toml` + (`[params.*]` / `[pdk.overrides]`) and takes effect on the next + fresh `ecc run`. + - `--workspace NAME`: written to `home/params.toml` `[params]`; + the refresh is immediate and the owning step is marked pending. + + Values: scalars are parsed per the reviewed schema type; list and object + values are JSON literals and arrays replace the previous value wholesale. + Invalid values fail with `invalid_value` and nothing is written. In the + workspace scope the owning step and its suffix are marked pending and a + later `ecc run --workspace NAME` resumes from that step. + + ```bash + ecc param set cts.skew_bound 0.05 + ecc param set cts.max_buf_tran 0.30 + ecc param set cts.routing_layer '[4, 5]' + ``` + + `pdk.*` path parameters are project-scope only; `pdk.root` is set with + `ecc pdk set-root`. + + See 'ecc doc config' for the full reference. + """ command_input = ParamSetInput( output=output_options(json_output=json_output, jsonl=jsonl, plain=plain), project=project_options(project), @@ -106,7 +148,7 @@ def set_cmd( _finish_param("set", command_input, param_set_handler) -@param_app.command("unset", help="Remove a parameter override") +@param_app.command("unset") def unset_cmd( *, key: Annotated[str, typer.Argument()], @@ -116,6 +158,15 @@ def unset_cmd( jsonl: JsonlOption = False, plain: PlainOption = False, ) -> None: + """Remove a parameter override. + + Project scope deletes the key from `ecc.toml`, restoring the default. + Workspace scope (`--workspace NAME`) restores the pre-edit `baseline` and + drops the override record; the owning step and its suffix stay invalid + until they run again. + + See 'ecc doc config' for the full reference. + """ command_input = ParamUnsetInput( output=output_options(json_output=json_output, jsonl=jsonl, plain=plain), project=project_options(project), @@ -125,7 +176,7 @@ def unset_cmd( _finish_param("unset", command_input, param_unset_handler) -@param_app.command("diff", help="Compare parameter overrides with defaults") +@param_app.command("diff") def diff_cmd( *, project: ProjectOption = None, @@ -134,6 +185,14 @@ def diff_cmd( jsonl: JsonlOption = False, plain: PlainOption = False, ) -> None: + """Compare parameter overrides with defaults. + + Project scope lists parameters whose value differs from the default. + Workspace scope (`--workspace NAME`) lists local overrides together with + their baselines. + + See 'ecc doc config' for the full reference. + """ command_input = ParamDiffInput( output=output_options(json_output=json_output, jsonl=jsonl, plain=plain), project=project_options(project), diff --git a/chipcompiler/cli/commands/pdk.py b/chipcompiler/cli/commands/pdk.py index bf7092d71..76cdda1d4 100644 --- a/chipcompiler/cli/commands/pdk.py +++ b/chipcompiler/cli/commands/pdk.py @@ -3,6 +3,7 @@ import typer from chipcompiler.cli.command_handlers import pdk as pdk_handlers +from chipcompiler.cli.core.apps import create_app from chipcompiler.cli.core.inputs import ( PdkSetRootInput, PdkSetupInput, @@ -19,12 +20,7 @@ ProjectOption, ) -pdk_app = typer.Typer( - add_completion=False, - no_args_is_help=True, - rich_markup_mode=None, - help="Show and configure the PDK path used by this project", -) +pdk_app = create_app(help="Show and configure the PDK path used by this project") def _finish(subcommand: str, command_input, handler) -> None: @@ -53,7 +49,7 @@ def setup_cmd( _finish("setup", command_input, pdk_handlers.setup) -@pdk_app.command("set-root", help="Set the [pdk] root path in ecc.toml") +@pdk_app.command("set-root") def set_root_cmd( *, path: Annotated[ @@ -65,6 +61,15 @@ def set_root_cmd( jsonl: JsonlOption = False, plain: PlainOption = False, ) -> None: + """Set the [pdk] root path in ecc.toml. + + `pdk.root` is the base directory for the PDK content paths `pdk.tech`, + `pdk.lefs`, `pdk.libs`, and `pdk.mapping_file`. The design-data paths + `pdk.sdc` and `pdk.spef` resolve against the project directory instead. + All six get file-existence validation. + + See 'ecc doc config' for the full reference. + """ command_input = PdkSetRootInput( output=output_options(json_output=json_output, jsonl=jsonl, plain=plain), project=project_options(project), diff --git a/chipcompiler/cli/commands/project.py b/chipcompiler/cli/commands/project.py index e6cbeafd0..6a8c99cc1 100644 --- a/chipcompiler/cli/commands/project.py +++ b/chipcompiler/cli/commands/project.py @@ -28,12 +28,12 @@ def register_project_commands(app: typer.Typer) -> None: app.command("init", help="Create a new ECC project")(init_cmd) app.command("check", help="Validate the current project setup")(check_cmd) - app.command("run", help="Run the configured RTL-to-GDS flow")(run_cmd) + app.command("run")(run_cmd) app.command( "status", help="Show a quick run/step progress summary (full evidence: 'ecc report step')" )(status_cmd) app.command("log", help="Show available logs or step log content")(log_cmd) - app.command("config", help="Show resolved project or step configuration")(config_cmd) + app.command("config")(config_cmd) app.command("migrate", help="Migrate a legacy runs/ project to the manifest layout")( migrate_cmd ) @@ -114,6 +114,22 @@ def run_cmd( ] = None, plain: PlainOption = False, ) -> None: + """Run the configured RTL-to-GDS flow. + + `--set KEY=VALUE` applies a one-off parameter override; it is accepted + only when the run creates a workspace (including `--overwrite`) and is + recorded in `home/cli-param-overrides.json`. On an existing workspace it + fails with `set_requires_fresh_run` — use + `ecc param set KEY VALUE --workspace NAME` instead. Precedence: + `--set` > `ecc.toml` `[params]` > defaults. + + Parameterized fields in `config/*.json` are re-refreshed from + `home/params.toml` and the PDK before every step, so manual edits are + overwritten. Each step reads the previous step's `output/`; the first + step reads the design's origin verilog/DEF. + + See 'ecc doc config' for the full reference. + """ command_input = RunInput( output=output_options(json_output=json_output, jsonl=jsonl, plain=plain), project=project_options(project), @@ -192,6 +208,15 @@ def config_cmd( plain: PlainOption = False, workspace: WorkspaceOption = None, ) -> None: + """Show resolved project or step configuration. + + Without STEP: resolved project-level configuration. With STEP: the + configuration files actually in effect for that step. `lec`, `lvs`, + `postroutelec`, and `harden` have no step-specific configuration + (Tcl-driven, tool-default, or reusing `db_ecc.json`). + + See 'ecc doc config' for the full reference. + """ command_input = ConfigInput( output=output_options(json_output=json_output, jsonl=jsonl, plain=plain), project=project_options(project), diff --git a/chipcompiler/cli/commands/project_config.py b/chipcompiler/cli/commands/project_config.py index 13ae5336a..f14c42ff4 100644 --- a/chipcompiler/cli/commands/project_config.py +++ b/chipcompiler/cli/commands/project_config.py @@ -5,6 +5,7 @@ import typer from chipcompiler.cli.command_handlers import project_config as handlers +from chipcompiler.cli.core.apps import create_app from chipcompiler.cli.core.inputs import ( ProjectAddInput, ProjectSetInput, @@ -16,12 +17,7 @@ from chipcompiler.cli.core.invocation import execute_command from chipcompiler.cli.core.options import JsonlOption, JsonOption, PlainOption, ProjectOption -project_app = typer.Typer( - add_completion=False, - no_args_is_help=True, - rich_markup_mode=None, - help="Edit project declarations in ecc.toml", -) +project_app = create_app(help="Edit project declarations in ecc.toml") def _finish(subcommand: str, command_input, handler) -> None: diff --git a/chipcompiler/cli/commands/report.py b/chipcompiler/cli/commands/report.py index e3d8525b0..cfa873ad8 100644 --- a/chipcompiler/cli/commands/report.py +++ b/chipcompiler/cli/commands/report.py @@ -3,6 +3,7 @@ import typer from chipcompiler.cli.command_handlers import report as report_handlers +from chipcompiler.cli.core.apps import create_app from chipcompiler.cli.core.inputs import ( ReportChecklistInput, ReportQorInput, @@ -20,16 +21,11 @@ WorkspaceOption, ) -report_app = typer.Typer( - add_completion=False, - no_args_is_help=True, - rich_markup_mode=None, - help="Generate design-summary, QoR score, checklist, and step reports", -) +report_app = create_app(help="Generate design-summary, QoR score, checklist, and step reports") OutputPathOption = Annotated[ str | None, - typer.Option("--output", "-o", help="Report destination (default: /signoff/)"), + typer.Option("--output", "-o", help="Report destination (default: ``/signoff/)"), ] diff --git a/chipcompiler/cli/commands/rpc.py b/chipcompiler/cli/commands/rpc.py index 48b46556f..184b96291 100644 --- a/chipcompiler/cli/commands/rpc.py +++ b/chipcompiler/cli/commands/rpc.py @@ -2,12 +2,9 @@ import typer -rpc_app = typer.Typer( - add_completion=False, - no_args_is_help=True, - rich_markup_mode=None, - help="Run the private ECC JSON-RPC runtime", -) +from chipcompiler.cli.core.apps import create_app + +rpc_app = create_app(help="Run the private ECC JSON-RPC runtime") @rpc_app.command("serve", help="Serve the private ECC JSON-RPC runtime") diff --git a/chipcompiler/cli/commands/signoff.py b/chipcompiler/cli/commands/signoff.py index 5a2994a1c..3dfcc4f5d 100644 --- a/chipcompiler/cli/commands/signoff.py +++ b/chipcompiler/cli/commands/signoff.py @@ -3,6 +3,7 @@ import typer from chipcompiler.cli.command_handlers import signoff as signoff_handlers +from chipcompiler.cli.core.apps import create_app from chipcompiler.cli.core.inputs import ( SignoffExportInput, SignoffInspectInput, @@ -18,12 +19,7 @@ WorkspaceOption, ) -signoff_app = typer.Typer( - add_completion=False, - no_args_is_help=True, - rich_markup_mode=None, - help="Inspect and export signoff packages", -) +signoff_app = create_app(help="Inspect and export signoff packages") def _finish(subcommand: str, command_input, handler) -> None: diff --git a/chipcompiler/cli/commands/workspace.py b/chipcompiler/cli/commands/workspace.py index 1a3030a62..2d5fb2b43 100644 --- a/chipcompiler/cli/commands/workspace.py +++ b/chipcompiler/cli/commands/workspace.py @@ -5,19 +5,15 @@ import typer from chipcompiler.cli.command_handlers import project as project_handlers +from chipcompiler.cli.core.apps import create_app from chipcompiler.cli.core.inputs import WorkspaceRefreshInput, output_options, project_options from chipcompiler.cli.core.invocation import execute_command from chipcompiler.cli.core.options import JsonlOption, JsonOption, PlainOption, ProjectOption -workspace_app = typer.Typer( - add_completion=False, - no_args_is_help=True, - rich_markup_mode=None, - help="Refresh managed workspaces from project configuration", -) +workspace_app = create_app(help="Refresh managed workspaces from project configuration") -@workspace_app.command("refresh", help="Recreate a workspace from ecc.toml without running it") +@workspace_app.command("refresh") def refresh_cmd( *, workspace: Annotated[str, typer.Argument(help="Declared workspace name")], @@ -26,6 +22,16 @@ def refresh_cmd( jsonl: JsonlOption = False, plain: PlainOption = False, ) -> None: + """Recreate a workspace from ecc.toml without running it. + + Rebuilds the generated configuration from `ecc.toml` and the PDK; manual + edits to `config/*.json` are overwritten. The workspace hub + `home/params.toml` keeps four sections: `[design]`, `[pdk]`, `[flow]`, + and `[params]`. `pdk.*` path changes cannot be applied with + `ecc param set --workspace`; edit `ecc.toml` and refresh instead. + + See 'ecc doc config' for the full reference. + """ command_input = WorkspaceRefreshInput( output=output_options(json_output=json_output, jsonl=jsonl, plain=plain), project=project_options(project), diff --git a/chipcompiler/cli/core/apps.py b/chipcompiler/cli/core/apps.py new file mode 100644 index 000000000..4b4e9ced2 --- /dev/null +++ b/chipcompiler/cli/core/apps.py @@ -0,0 +1,10 @@ +import typer + + +def create_app(*, help: str, add_completion: bool = False) -> typer.Typer: + return typer.Typer( + add_completion=add_completion, + no_args_is_help=True, + rich_markup_mode="markdown", + help=help, + ) diff --git a/chipcompiler/cli/core/docs.py b/chipcompiler/cli/core/docs.py new file mode 100644 index 000000000..f669731af --- /dev/null +++ b/chipcompiler/cli/core/docs.py @@ -0,0 +1,32 @@ +"""Locate and load the bundled CLI guide documents.""" + +import sys +from importlib import resources +from importlib.resources.abc import Traversable +from pathlib import Path + +GUIDE_STEMS = { + "config": "ecc-cli-config", + "ug": "ecc-cli-ug", + "tutorial": "ecc-cli-tutorial", + "dev": "ecc-cli-dev", +} + + +class GuideNotFoundError(FileNotFoundError): + pass + + +def guides_root() -> Traversable: + bundle_root = getattr(sys, "_MEIPASS", None) + if bundle_root: + return Path(bundle_root) / "docs" + return resources.files("chipcompiler") / "docs" + + +def load_guide(topic: str, lang: str) -> bytes: + name = f"{GUIDE_STEMS[topic]}.{lang}.md" + path = guides_root() / name + if not path.is_file(): + raise GuideNotFoundError(f"doc resource not found: {name}") + return path.read_bytes() diff --git a/chipcompiler/cli/rendering/render.py b/chipcompiler/cli/rendering/render.py index eaeda96a3..fbf0d480d 100644 --- a/chipcompiler/cli/rendering/render.py +++ b/chipcompiler/cli/rendering/render.py @@ -1,4 +1,5 @@ import json +import os import sys from chipcompiler.cli.core.types import CommandResult, OutputMode @@ -50,6 +51,32 @@ def _plain_value(value) -> str: return s +def render_markdown(text: str, file=None, *, color: bool, pager: bool = False) -> None: + from rich.console import Console + from rich.markdown import Markdown + + # force_terminal tracks color: forcing a terminal on a colorless stream + # makes rich 15 emit ANSI escapes even with no_color=True. + console = Console(file=file or sys.stdout, force_terminal=color, no_color=not color) + # Page only on a real terminal: pydoc picks its pager at import time, so + # its own isatty check cannot be trusted once the process has been piped. + if pager and file is None and sys.stdout.isatty(): + # pydoc invokes plain `less`, which escapes ANSI; with no user LESS, + # default to git's FRX so styled output renders and short docs don't + # open the pager UI at all. + saved_less = os.environ.get("LESS") + if saved_less is None: + os.environ["LESS"] = "FRX" + try: + with console.pager(styles=color): + console.print(Markdown(text)) + finally: + if saved_less is None: + del os.environ["LESS"] + else: + console.print(Markdown(text)) + + def render_result( result: CommandResult, mode: OutputMode, file=None, command=None, *, color=True ) -> None: diff --git a/docs/ecc-cli-config.cn.md b/chipcompiler/docs/ecc-cli-config.cn.md similarity index 84% rename from docs/ecc-cli-config.cn.md rename to chipcompiler/docs/ecc-cli-config.cn.md index ca31769fc..1114bda03 100644 --- a/docs/ecc-cli-config.cn.md +++ b/chipcompiler/docs/ecc-cli-config.cn.md @@ -1,6 +1,6 @@ # ECC Flow 工具配置参考(按步骤) -本文整理 ECC RTL-to-Harden 流程中**每一步实际使用的工具配置文件、全部参数及其含义**。配置取值与生成逻辑均核对自 v0.1.0-alpha.11 源码(rebase main 之后;模板位于 [chipcompiler/tools/*/configs/](../chipcompiler/tools/ecc/configs/))与一次真实的 gcd@ics55 harden 运行。 +本文整理 ECC RTL-to-Harden 流程中**每一步实际使用的工具配置文件、全部参数及其含义**。配置取值与生成逻辑均核对自 v0.1.0-alpha.11 源码(rebase main 之后;模板位于 [chipcompiler/tools/*/configs/](../../chipcompiler/tools/ecc/configs/))与一次真实的 gcd@ics55 harden 运行。 - 想了解命令用法 → [ECC CLI 用户指南](ecc-cli-ug.cn.md);从零上手 → [入门教程](ecc-cli-tutorial.cn.md) - 配置查看命令:`ecc config `(列出该步骤实际生效的配置文件);参数查看与修改命令:`ecc param`(见 §1.4) @@ -61,7 +61,7 @@ graph LR ### 0.3 每个步骤用到哪些配置 -`ecc config ` 的真实输出归纳(映射源码 `_STEP_CONFIG_KEYS`,位于 [chipcompiler/data/workspace/__init__.py](../chipcompiler/data/workspace/__init__.py)): +`ecc config ` 的真实输出归纳(映射源码 `_STEP_CONFIG_KEYS`,位于 [chipcompiler/data/workspace/__init__.py](../../chipcompiler/data/workspace/__init__.py)): | 步骤 | db_ecc | 专属配置 | 说明 | |---|---|---|---| @@ -85,7 +85,7 @@ graph LR ### 1.1 旧语义参数(13 个) -来源:[chipcompiler/cli/project/params.py](../chipcompiler/cli/project/params.py) 的 `_LEGACY_PARAM_REGISTRY`(`PARAM_REGISTRY` 的兼容段;直配参数见 §1.2 的 `config_params/` schema)。这些参数保持兼容;优先级:`--set` > `ecc.toml [params]` > 默认值。「写入位置」列为该参数最终落到的工具配置字段。 +来源:[chipcompiler/cli/project/params.py](../../chipcompiler/cli/project/params.py) 的 `_LEGACY_PARAM_REGISTRY`(`PARAM_REGISTRY` 的兼容段;直配参数见 §1.2 的 `config_params/` schema)。这些参数保持兼容;优先级:`--set` > `ecc.toml [params]` > 默认值。「写入位置」列为该参数最终落到的工具配置字段。 | 参数 | 类型 / 范围 | 默认 | 写入位置(config 字段) | 含义 | |---|---|---|---|---| @@ -152,7 +152,7 @@ tech = "prtech/techLEF/N551P6M_ecos.lef" ### 1.4 参数配置 CLI 命令(`ecc param`) -参数的查看与修改统一走 `ecc param` 子命令(子命令定义:[chipcompiler/cli/commands/param.py](../chipcompiler/cli/commands/param.py);项目作用域实现:[chipcompiler/cli/command_handlers/param.py](../chipcompiler/cli/command_handlers/param.py),workspace 作用域实现:[chipcompiler/cli/command_handlers/workspace_params.py](../chipcompiler/cli/command_handlers/workspace_params.py)): +参数的查看与修改统一走 `ecc param` 子命令(子命令定义:[chipcompiler/cli/commands/param.py](../../chipcompiler/cli/commands/param.py);项目作用域实现:[chipcompiler/cli/command_handlers/param.py](../../chipcompiler/cli/command_handlers/param.py),workspace 作用域实现:[chipcompiler/cli/command_handlers/workspace_params.py](../../chipcompiler/cli/command_handlers/workspace_params.py)): | 命令 | 作用 | |---|---| @@ -503,7 +503,7 @@ ics55 的 corner 命名:`Cworst/Cbest`=电容最差/最好,`RCworst/RCbest`= ## 15. 各步骤的 ECC CLI 配置命令 -以下清单将前文的配置字段对应到可执行的 ECC CLI 命令。`VALUE` 是待替换的值占位符;列表和对象必须传入 JSON 字面量,例如 `ecc param set cts.routing_layer '[4, 5]'`。命令默认写入项目的 `ecc.toml`;要修改已创建的 workspace,请在命令末尾追加 `--workspace NAME`(PDK 路径参数除外,见 §1.4)。先执行列出的 `ecc param list --step STEP` 可查看当前版本完整的可调字段、默认值和约束。 +所有字段的设置方式相同——`ecc param set KEY VALUE`——因此每个步骤只给出少量代表性示例;执行列出的 `ecc param list --step STEP` 可查看当前版本完整的可调字段、默认值和约束。`VALUE` 是待替换的值占位符;列表和对象必须传入 JSON 字面量,例如 `ecc param set cts.routing_layer '[4, 5]'`。命令默认写入项目的 `ecc.toml`;要修改已创建的 workspace,请在命令末尾追加 `--workspace NAME`(PDK 路径参数除外,见 §1.4)。 ### 15.1 公共 db 配置与 PDK 路径 @@ -511,12 +511,7 @@ ics55 的 corner 命名:`Cworst/Cbest`=电容最差/最好,`RCworst/RCbest`= ```bash ecc param list --step pdk -ecc param set pdk.tech VALUE -ecc param set pdk.lefs VALUE -ecc param set pdk.libs VALUE -ecc param set pdk.mapping_file VALUE -ecc param set pdk.sdc VALUE -ecc param set pdk.spef VALUE +ecc param set pdk.tech VALUE # pdk.lefs、pdk.libs、pdk.mapping_file、pdk.sdc、pdk.spef 用法相同 ``` `pdk.root` 使用 `ecc pdk set-root PATH`,不属于 `ecc param`。绕线首层由 routing 步骤的 `route.bottom_layer` 设置,见 §15.6。 @@ -532,34 +527,13 @@ ecc param set design.frequency_mhz VALUE ### 15.3 floorplan -`temp_directory_path` 与 `macro_location_path` 为流程生成/保护路径,不能通过 CLI 设置;其余已审核的 `floorplan_ecc.json` 字段使用: +`temp_directory_path` 与 `macro_location_path` 为流程生成/保护路径,不能通过 CLI 设置;其余已审核的 `floorplan_ecc.json` 字段用法相同,例如: ```bash ecc param list --step floorplan ecc param set floorplan.core_util VALUE -ecc param set floorplan.core_margin VALUE -ecc param set floorplan.aspect_ratio VALUE -ecc param set floorplan.die_builder.die_size.width_micron VALUE -ecc param set floorplan.die_builder.die_size.height_micron VALUE -ecc param set floorplan.die_builder.mode VALUE -ecc param set floorplan.die_builder.site_name VALUE -ecc param set floorplan.ifp.thread_number VALUE -ecc param set floorplan.io_placer.io_layer_list VALUE -ecc param set floorplan.macro_placer.macro_placement_halo VALUE -ecc param set floorplan.macro_placer.macro_routing_halo VALUE -ecc param set floorplan.pdn_generator.global_connect VALUE -ecc param set floorplan.pdn_generator.rail VALUE -ecc param set floorplan.pdn_generator.stripe VALUE -ecc param set floorplan.pdn_generator.connect_layers VALUE -ecc param set floorplan.phy_placer.well_tap.cell_name VALUE +ecc param set floorplan.die_builder.die_size.width_micron VALUE # 嵌套字段使用点路径 ecc param set floorplan.phy_placer.well_tap.distance_micron VALUE -ecc param set floorplan.phy_placer.side_endcap.left_cell_name VALUE -ecc param set floorplan.phy_placer.side_endcap.right_cell_name VALUE -ecc param set floorplan.phy_placer.edge_endcap.top_cell_name_list VALUE -ecc param set floorplan.phy_placer.edge_endcap.bottom_cell_name_list VALUE -ecc param set floorplan.phy_placer.boundary_tap.top_cell_name_list VALUE -ecc param set floorplan.phy_placer.boundary_tap.bottom_cell_name_list VALUE -ecc param set floorplan.phy_placer.boundary_tap.rule_micron VALUE ``` ### 15.4 placement / legalization @@ -569,91 +543,7 @@ placement 与 legalization 共用 `dreamplace_ecc.json`,因此使用同一组 ```bash ecc param list --step placement ecc param set place.target_density VALUE -ecc param set place.target_overflow VALUE -ecc param set place.cell_padding_x VALUE -ecc param set place.routability_opt VALUE -ecc param set place.RePlAce_LOWER_PCOF VALUE -ecc param set place.RePlAce_UPPER_PCOF VALUE -ecc param set place.RePlAce_ref_hpwl VALUE -ecc param set place.RePlAce_skip_energy_flag VALUE -ecc param set place.adjust_nctugr_area_flag VALUE -ecc param set place.adjust_pin_area_flag VALUE -ecc param set place.adjust_rudy_area_flag VALUE -ecc param set place.area_adjust_stop_ratio VALUE -ecc param set place.auto_adjust_bins VALUE -ecc param set place.bndry_padding_x VALUE -ecc param set place.bndry_padding_y VALUE -ecc param set place.density_weight VALUE -ecc param set place.detailed_place_command VALUE -ecc param set place.detailed_place_engine VALUE -ecc param set place.detailed_place_flag VALUE -ecc param set place.deterministic_flag VALUE -ecc param set place.differentiable_timing_obj VALUE -ecc param set place.dtype VALUE -ecc param set place.dump_global_place_solution_flag VALUE -ecc param set place.dump_legalize_solution_flag VALUE -ecc param set place.enable_fillers VALUE -ecc param set place.enable_net_weighting VALUE -ecc param set place.evaluate_pl VALUE -ecc param set place.gamma VALUE -ecc param set place.get_congestion_map VALUE -ecc param set place.global_place_flag VALUE -ecc param set place.global_place_stages VALUE -ecc param set place.gp_noise_ratio VALUE -ecc param set place.gpu VALUE -ecc param set place.gpu_id VALUE -ecc param set place.ignore_net_degree VALUE -ecc param set place.ignore_net_weight VALUE -ecc param set place.init_loc_perc_x VALUE -ecc param set place.init_loc_perc_y VALUE -ecc param set place.legalize_flag VALUE -ecc param set place.macro_halo_x VALUE -ecc param set place.macro_halo_y VALUE -ecc param set place.macro_overlap_flag VALUE -ecc param set place.macro_overlap_mult_weight VALUE -ecc param set place.macro_overlap_weight VALUE -ecc param set place.macro_pin_halo_x VALUE -ecc param set place.macro_pin_halo_y VALUE -ecc param set place.macro_place_flag VALUE -ecc param set place.max_net_weight VALUE -ecc param set place.max_num_area_adjust VALUE -ecc param set place.max_pin_opt_adjust_rate VALUE -ecc param set place.max_route_opt_adjust_rate VALUE -ecc param set place.momentum_decay_factor VALUE -ecc param set place.net_weighting_scheme VALUE -ecc param set place.node_area_adjust_overflow VALUE -ecc param set place.num_bins_x VALUE -ecc param set place.num_bins_y VALUE ecc param set place.num_threads VALUE -ecc param set place.pin2pin_accumulate_weight VALUE -ecc param set place.pin2pin_max_weight VALUE -ecc param set place.pin2pin_min_weight VALUE -ecc param set place.pin2pin_net_weighting VALUE -ecc param set place.pin2pin_weight VALUE -ecc param set place.pin_area_adjust_stop_ratio VALUE -ecc param set place.pin_density VALUE -ecc param set place.pin_stretch_ratio VALUE -ecc param set place.plot_flag VALUE -ecc param set place.random_center_init_flag VALUE -ecc param set place.random_seed VALUE -ecc param set place.risa_weights VALUE -ecc param set place.route_area_adjust_stop_ratio VALUE -ecc param set place.route_info_input VALUE -ecc param set place.route_num_bins_x VALUE -ecc param set place.route_num_bins_y VALUE -ecc param set place.route_opt_adjust_exponent VALUE -ecc param set place.scale_factor VALUE -ecc param set place.shift_factor VALUE -ecc param set place.sort_nets_by_degree VALUE -ecc param set place.start_iter VALUE -ecc param set place.timing_eval_flag VALUE -ecc param set place.timing_opt_flag VALUE -ecc param set place.two_stage_density_scaler VALUE -ecc param set place.unit_horizontal_capacity VALUE -ecc param set place.unit_pin_capacity VALUE -ecc param set place.unit_vertical_capacity VALUE -ecc param set place.use_bb VALUE -ecc param set place.with_sta VALUE ``` ### 15.5 timing optimization @@ -666,26 +556,13 @@ Sizer 没有专属 `ecc param` schema。其内部 DreamPlace 合法化使用 §1 ecc param list --step cts ecc param set cts.max_fanout VALUE ecc param set cts.skew_bound VALUE -ecc param set cts.max_buf_tran VALUE -ecc param set cts.root_input_slew VALUE -ecc param set cts.max_sink_tran VALUE -ecc param set cts.max_cap VALUE -ecc param set cts.max_length VALUE -ecc param set cts.wirelength_iterations VALUE -ecc param set cts.slew_steps VALUE -ecc param set cts.cap_steps VALUE ecc param set cts.routing_layer VALUE ecc param set cts.buffer_type VALUE -ecc param set cts.use_netlist VALUE -ecc param set cts.net_list VALUE ecc param list --step routing ecc param set route.bottom_layer VALUE ecc param set route.top_layer VALUE ecc param set route.RT.-thread_number VALUE -ecc param set route.RT.-enable_timing VALUE -ecc param set route.RT.-output_csv VALUE -ecc param set route.RT.-output_inter_result VALUE ``` `route_ecc.json` 的临时目录由步骤调度生成,不能通过 CLI 设置。 diff --git a/docs/ecc-cli-config.en.md b/chipcompiler/docs/ecc-cli-config.en.md similarity index 85% rename from docs/ecc-cli-config.en.md rename to chipcompiler/docs/ecc-cli-config.en.md index 4e5623bb1..d9dcc2750 100644 --- a/docs/ecc-cli-config.en.md +++ b/chipcompiler/docs/ecc-cli-config.en.md @@ -1,6 +1,6 @@ # ECC Flow Tool Configuration Reference (by step) -This document consolidates **the tool configuration files actually used by each step of the ECC RTL-to-Harden flow, all of their parameters, and what each parameter means**. Configuration values and generation logic were verified against the v0.1.0-alpha.11 source (after the rebase onto main; templates live in [chipcompiler/tools/*/configs/](../chipcompiler/tools/ecc/configs/)) and a real gcd@ics55 harden run. +This document consolidates **the tool configuration files actually used by each step of the ECC RTL-to-Harden flow, all of their parameters, and what each parameter means**. Configuration values and generation logic were verified against the v0.1.0-alpha.11 source (after the rebase onto main; templates live in [chipcompiler/tools/*/configs/](../../chipcompiler/tools/ecc/configs/)) and a real gcd@ics55 harden run. - For command usage, see the [ECC CLI User Guide](ecc-cli-ug.en.md); to get started from scratch, see the [Tutorial](ecc-cli-tutorial.en.md) - Config inspection command: `ecc config ` (lists the configuration files actually in effect for that step); parameter inspection/modification command: `ecc param` (see §1.4) @@ -61,7 +61,7 @@ graph LR ### 0.3 Which configurations each step uses -Distilled from real `ecc config ` output (maps to the source `_STEP_CONFIG_KEYS` in [chipcompiler/data/workspace/__init__.py](../chipcompiler/data/workspace/__init__.py)): +Distilled from real `ecc config ` output (maps to the source `_STEP_CONFIG_KEYS` in [chipcompiler/data/workspace/__init__.py](../../chipcompiler/data/workspace/__init__.py)): | Step | db_ecc | Step-specific config | Notes | |---|---|---|---| @@ -85,7 +85,7 @@ Distilled from real `ecc config ` output (maps to the source `_STEP_CONFIG ### 1.1 Legacy-semantic parameters (13) -Source: `_LEGACY_PARAM_REGISTRY` in [chipcompiler/cli/project/params.py](../chipcompiler/cli/project/params.py) (the compatibility section of `PARAM_REGISTRY`; the direct-config parameters are the `config_params/` schemas in §1.2). These parameters are kept for compatibility; precedence: `--set` > `ecc.toml [params]` > defaults. The "Written to" column shows the tool configuration field each parameter ultimately lands in. +Source: `_LEGACY_PARAM_REGISTRY` in [chipcompiler/cli/project/params.py](../../chipcompiler/cli/project/params.py) (the compatibility section of `PARAM_REGISTRY`; the direct-config parameters are the `config_params/` schemas in §1.2). These parameters are kept for compatibility; precedence: `--set` > `ecc.toml [params]` > defaults. The "Written to" column shows the tool configuration field each parameter ultimately lands in. | Parameter | Type / range | Default | Written to (config field) | Meaning | |---|---|---|---|---| @@ -150,7 +150,7 @@ Workspace-local overrides written by `ecc param set KEY VALUE --workspace NAME` ### 1.4 Parameter-configuration CLI commands (`ecc param`) -Parameter inspection and modification go through the `ecc param` subcommands (subcommand definitions: [chipcompiler/cli/commands/param.py](../chipcompiler/cli/commands/param.py); project-scope implementation: [chipcompiler/cli/command_handlers/param.py](../chipcompiler/cli/command_handlers/param.py), workspace-scope implementation: [chipcompiler/cli/command_handlers/workspace_params.py](../chipcompiler/cli/command_handlers/workspace_params.py)): +Parameter inspection and modification go through the `ecc param` subcommands (subcommand definitions: [chipcompiler/cli/commands/param.py](../../chipcompiler/cli/commands/param.py); project-scope implementation: [chipcompiler/cli/command_handlers/param.py](../../chipcompiler/cli/command_handlers/param.py), workspace-scope implementation: [chipcompiler/cli/command_handlers/workspace_params.py](../../chipcompiler/cli/command_handlers/workspace_params.py)): | Command | What it does | |---|---| @@ -501,7 +501,7 @@ No step-specific configuration file: it reuses `db_ecc.json` to locate its input ## 15. ECC CLI configuration commands by step -The following lists map the preceding configuration fields to executable ECC CLI commands. `VALUE` is a value placeholder; lists and objects must be passed as JSON literals, for example `ecc param set cts.routing_layer '[4, 5]'`. Commands write to the project `ecc.toml` by default; to change an existing workspace, append `--workspace NAME` (except for PDK path parameters; see §1.4). Run the listed `ecc param list --step STEP` first to see all tunable fields, defaults, and constraints in the current version. +Every field is set the same way — `ecc param set KEY VALUE` — so each step below shows only a few representative keys; run the listed `ecc param list --step STEP` for the complete set of tunable fields, defaults, and constraints in the current version. `VALUE` is a value placeholder; lists and objects must be passed as JSON literals, for example `ecc param set cts.routing_layer '[4, 5]'`. Commands write to the project `ecc.toml` by default; to change an existing workspace, append `--workspace NAME` (except for PDK path parameters; see §1.4). ### 15.1 Shared db configuration and PDK paths @@ -509,12 +509,7 @@ The DEF, netlist, and output paths in `db_ecc.json` are generated by step schedu ```bash ecc param list --step pdk -ecc param set pdk.tech VALUE -ecc param set pdk.lefs VALUE -ecc param set pdk.libs VALUE -ecc param set pdk.mapping_file VALUE -ecc param set pdk.sdc VALUE -ecc param set pdk.spef VALUE +ecc param set pdk.tech VALUE # likewise pdk.lefs, pdk.libs, pdk.mapping_file, pdk.sdc, pdk.spef ``` Use `ecc pdk set-root PATH` for `pdk.root`; it is not an `ecc param` field. The first routing layer is set by `route.bottom_layer` in the routing step; see §15.6. @@ -530,34 +525,13 @@ ecc param set design.frequency_mhz VALUE ### 15.3 floorplan -`temp_directory_path` and `macro_location_path` are generated/protected paths and cannot be set through the CLI. Use the following commands for the other reviewed `floorplan_ecc.json` fields: +`temp_directory_path` and `macro_location_path` are generated/protected paths and cannot be set through the CLI. The other reviewed `floorplan_ecc.json` fields are set the same way, for example: ```bash ecc param list --step floorplan ecc param set floorplan.core_util VALUE -ecc param set floorplan.core_margin VALUE -ecc param set floorplan.aspect_ratio VALUE -ecc param set floorplan.die_builder.die_size.width_micron VALUE -ecc param set floorplan.die_builder.die_size.height_micron VALUE -ecc param set floorplan.die_builder.mode VALUE -ecc param set floorplan.die_builder.site_name VALUE -ecc param set floorplan.ifp.thread_number VALUE -ecc param set floorplan.io_placer.io_layer_list VALUE -ecc param set floorplan.macro_placer.macro_placement_halo VALUE -ecc param set floorplan.macro_placer.macro_routing_halo VALUE -ecc param set floorplan.pdn_generator.global_connect VALUE -ecc param set floorplan.pdn_generator.rail VALUE -ecc param set floorplan.pdn_generator.stripe VALUE -ecc param set floorplan.pdn_generator.connect_layers VALUE -ecc param set floorplan.phy_placer.well_tap.cell_name VALUE +ecc param set floorplan.die_builder.die_size.width_micron VALUE # nested fields use dot paths ecc param set floorplan.phy_placer.well_tap.distance_micron VALUE -ecc param set floorplan.phy_placer.side_endcap.left_cell_name VALUE -ecc param set floorplan.phy_placer.side_endcap.right_cell_name VALUE -ecc param set floorplan.phy_placer.edge_endcap.top_cell_name_list VALUE -ecc param set floorplan.phy_placer.edge_endcap.bottom_cell_name_list VALUE -ecc param set floorplan.phy_placer.boundary_tap.top_cell_name_list VALUE -ecc param set floorplan.phy_placer.boundary_tap.bottom_cell_name_list VALUE -ecc param set floorplan.phy_placer.boundary_tap.rule_micron VALUE ``` ### 15.4 placement / legalization @@ -567,91 +541,7 @@ Placement and legalization share `dreamplace_ecc.json`, so they use the same `pl ```bash ecc param list --step placement ecc param set place.target_density VALUE -ecc param set place.target_overflow VALUE -ecc param set place.cell_padding_x VALUE -ecc param set place.routability_opt VALUE -ecc param set place.RePlAce_LOWER_PCOF VALUE -ecc param set place.RePlAce_UPPER_PCOF VALUE -ecc param set place.RePlAce_ref_hpwl VALUE -ecc param set place.RePlAce_skip_energy_flag VALUE -ecc param set place.adjust_nctugr_area_flag VALUE -ecc param set place.adjust_pin_area_flag VALUE -ecc param set place.adjust_rudy_area_flag VALUE -ecc param set place.area_adjust_stop_ratio VALUE -ecc param set place.auto_adjust_bins VALUE -ecc param set place.bndry_padding_x VALUE -ecc param set place.bndry_padding_y VALUE -ecc param set place.density_weight VALUE -ecc param set place.detailed_place_command VALUE -ecc param set place.detailed_place_engine VALUE -ecc param set place.detailed_place_flag VALUE -ecc param set place.deterministic_flag VALUE -ecc param set place.differentiable_timing_obj VALUE -ecc param set place.dtype VALUE -ecc param set place.dump_global_place_solution_flag VALUE -ecc param set place.dump_legalize_solution_flag VALUE -ecc param set place.enable_fillers VALUE -ecc param set place.enable_net_weighting VALUE -ecc param set place.evaluate_pl VALUE -ecc param set place.gamma VALUE -ecc param set place.get_congestion_map VALUE -ecc param set place.global_place_flag VALUE -ecc param set place.global_place_stages VALUE -ecc param set place.gp_noise_ratio VALUE -ecc param set place.gpu VALUE -ecc param set place.gpu_id VALUE -ecc param set place.ignore_net_degree VALUE -ecc param set place.ignore_net_weight VALUE -ecc param set place.init_loc_perc_x VALUE -ecc param set place.init_loc_perc_y VALUE -ecc param set place.legalize_flag VALUE -ecc param set place.macro_halo_x VALUE -ecc param set place.macro_halo_y VALUE -ecc param set place.macro_overlap_flag VALUE -ecc param set place.macro_overlap_mult_weight VALUE -ecc param set place.macro_overlap_weight VALUE -ecc param set place.macro_pin_halo_x VALUE -ecc param set place.macro_pin_halo_y VALUE -ecc param set place.macro_place_flag VALUE -ecc param set place.max_net_weight VALUE -ecc param set place.max_num_area_adjust VALUE -ecc param set place.max_pin_opt_adjust_rate VALUE -ecc param set place.max_route_opt_adjust_rate VALUE -ecc param set place.momentum_decay_factor VALUE -ecc param set place.net_weighting_scheme VALUE -ecc param set place.node_area_adjust_overflow VALUE -ecc param set place.num_bins_x VALUE -ecc param set place.num_bins_y VALUE ecc param set place.num_threads VALUE -ecc param set place.pin2pin_accumulate_weight VALUE -ecc param set place.pin2pin_max_weight VALUE -ecc param set place.pin2pin_min_weight VALUE -ecc param set place.pin2pin_net_weighting VALUE -ecc param set place.pin2pin_weight VALUE -ecc param set place.pin_area_adjust_stop_ratio VALUE -ecc param set place.pin_density VALUE -ecc param set place.pin_stretch_ratio VALUE -ecc param set place.plot_flag VALUE -ecc param set place.random_center_init_flag VALUE -ecc param set place.random_seed VALUE -ecc param set place.risa_weights VALUE -ecc param set place.route_area_adjust_stop_ratio VALUE -ecc param set place.route_info_input VALUE -ecc param set place.route_num_bins_x VALUE -ecc param set place.route_num_bins_y VALUE -ecc param set place.route_opt_adjust_exponent VALUE -ecc param set place.scale_factor VALUE -ecc param set place.shift_factor VALUE -ecc param set place.sort_nets_by_degree VALUE -ecc param set place.start_iter VALUE -ecc param set place.timing_eval_flag VALUE -ecc param set place.timing_opt_flag VALUE -ecc param set place.two_stage_density_scaler VALUE -ecc param set place.unit_horizontal_capacity VALUE -ecc param set place.unit_pin_capacity VALUE -ecc param set place.unit_vertical_capacity VALUE -ecc param set place.use_bb VALUE -ecc param set place.with_sta VALUE ``` ### 15.5 timing optimization @@ -664,26 +554,13 @@ Sizer has no dedicated `ecc param` schema. Its internal DreamPlace legalization ecc param list --step cts ecc param set cts.max_fanout VALUE ecc param set cts.skew_bound VALUE -ecc param set cts.max_buf_tran VALUE -ecc param set cts.root_input_slew VALUE -ecc param set cts.max_sink_tran VALUE -ecc param set cts.max_cap VALUE -ecc param set cts.max_length VALUE -ecc param set cts.wirelength_iterations VALUE -ecc param set cts.slew_steps VALUE -ecc param set cts.cap_steps VALUE ecc param set cts.routing_layer VALUE ecc param set cts.buffer_type VALUE -ecc param set cts.use_netlist VALUE -ecc param set cts.net_list VALUE ecc param list --step routing ecc param set route.bottom_layer VALUE ecc param set route.top_layer VALUE ecc param set route.RT.-thread_number VALUE -ecc param set route.RT.-enable_timing VALUE -ecc param set route.RT.-output_csv VALUE -ecc param set route.RT.-output_inter_result VALUE ``` The temporary directory in `route_ecc.json` is generated by step scheduling and cannot be set through the CLI. diff --git a/docs/ecc-cli-dev.cn.md b/chipcompiler/docs/ecc-cli-dev.cn.md similarity index 96% rename from docs/ecc-cli-dev.cn.md rename to chipcompiler/docs/ecc-cli-dev.cn.md index 068e54e53..cb17028f4 100644 --- a/docs/ecc-cli-dev.cn.md +++ b/chipcompiler/docs/ecc-cli-dev.cn.md @@ -2,7 +2,7 @@ 本文面向需要在 `ecc` CLI 中新增/修改命令的开发者,基于 `ecc/` 子模块当前源码(`chipcompiler` 包,v0.1.0-alpha.11)整理。代码路径均相对 `ecc/` 子模块根目录。 -相关文档:[architecture.md](architecture.md)(架构)、[development.md](development.md)(开发工作流)、[workspace-cli.md](workspace-cli.md)(RPC sidecar 协议)、[../CLAUDE.md](../CLAUDE.md)(仓库约定)。 +相关文档:[architecture.md](../../docs/architecture.md)(架构)、[development.md](../../docs/development.md)(开发工作流)、[workspace-cli.md](../../docs/workspace-cli.md)(RPC sidecar 协议)、[../../CLAUDE.md](../../CLAUDE.md)(仓库约定)。 ## 1. 入口与整体结构 @@ -227,7 +227,7 @@ config_param( ### 5.6 扩展 RPC(`ecc rpc serve`) -`rpc serve --stdio` 启动 JSON-RPC 2.0 sidecar(`chipcompiler/runtime/stdio_server.py`)。方法在 `chipcompiler/runtime/methods.py::RUNTIME_METHODS` 声明(`method_name` + pydantic `request_model` + `handler_name`),handler 实现在 `chipcompiler/runtime/workspace_api.py`,由 `runtime/server.py` 统一挂载;协议细节见 [workspace-cli.md](workspace-cli.md)。新增方法 = 加一个 `RuntimeMethodSpec` + 对应 API 方法 + 请求模型,无需改 CLI 层。 +`rpc serve --stdio` 启动 JSON-RPC 2.0 sidecar(`chipcompiler/runtime/stdio_server.py`)。方法在 `chipcompiler/runtime/methods.py::RUNTIME_METHODS` 声明(`method_name` + pydantic `request_model` + `handler_name`),handler 实现在 `chipcompiler/runtime/workspace_api.py`,由 `runtime/server.py` 统一挂载;协议细节见 [workspace-cli.md](../../docs/workspace-cli.md)。新增方法 = 加一个 `RuntimeMethodSpec` + 对应 API 方法 + 请求模型,无需改 CLI 层。 ### 5.7 扩展项目声明(`ecc project *` / `ecc workspace refresh`) @@ -250,14 +250,14 @@ rm -rf ~/.local/ecc && mkdir -p ~/.local/ecc && cp -a dist/ecc/. ~/.local/ecc/ ecc --help # 验证 doctor / signoff / report 已列出 ``` -回退官方发行版:重新运行 [README](../README.cn.md#安装) 的安装脚本(`curl -fsSL http://release.openecos.com/installers/ecc/latest/ecc-installer.sh | sh`)。 +回退官方发行版:重新运行 [README](../../README.cn.md#安装) 的安装脚本(`curl -fsSL http://release.openecos.com/installers/ecc/latest/ecc-installer.sh | sh`)。 ## 7. 约束与注意事项(来自仓库约定) -- **模块体积**:文件超过约 800 LoC 时新功能放新模块,不要继续堆([../CLAUDE.md](../CLAUDE.md) 第 6 节)。 +- **模块体积**:文件超过约 800 LoC 时新功能放新模块,不要继续堆([../../CLAUDE.md](../../CLAUDE.md) 第 6 节)。 - **Python 3+**:不用 `__future__`;最低版本看 `pyproject.toml` 的 `requires-python`。 - **测试放置**按所有权边界;优先整对象比较;不为静态定义的值写测试;不为已删除的逻辑保留负向测试。 -- **代码评审**必须执行 [review-guidelines.md](review-guidelines.md) 的附加标准。 +- **代码评审**必须执行 [review-guidelines.md](../../docs/review-guidelines.md) 的附加标准。 - `uv.lock` 是依赖事实源;`requirements_lock.txt` 自动生成且被 gitignore。 - ECC-Tools 在代码里的工具标识是 `"ecc"`(不是 `"ecc-tools"`);每个工具模块需实现 `is_eda_exist / build_step / run_step`;步骤在 `multiprocessing.Process` 中执行,状态持久化在 `workspace.flow.json`。 - 依赖安装后 `ecc` 以 editable 方式生效,改源码下次导入即生效,无需重装。 diff --git a/docs/ecc-cli-dev.en.md b/chipcompiler/docs/ecc-cli-dev.en.md similarity index 97% rename from docs/ecc-cli-dev.en.md rename to chipcompiler/docs/ecc-cli-dev.en.md index 36daa7794..52282a412 100644 --- a/docs/ecc-cli-dev.en.md +++ b/chipcompiler/docs/ecc-cli-dev.en.md @@ -2,7 +2,7 @@ This guide is for developers who need to add or modify commands in the `ecc` CLI. It is based on the current source tree (the `chipcompiler` package, v0.1.0-alpha.11). All code paths are relative to the `ecc` repository root. -Related documents: [architecture.md](architecture.md) (architecture), [development.md](development.md) (development workflow), [workspace-cli.md](workspace-cli.md) (RPC sidecar protocol), [../CLAUDE.md](../CLAUDE.md) (repository conventions). +Related documents: [architecture.md](../../docs/architecture.md) (architecture), [development.md](../../docs/development.md) (development workflow), [workspace-cli.md](../../docs/workspace-cli.md) (RPC sidecar protocol), [../../CLAUDE.md](../../CLAUDE.md) (repository conventions). ## 1. Entry point and overall structure @@ -227,7 +227,7 @@ Project preset sequences are defined in `chipcompiler/rtl2gds/builder.py` (`buil ### 5.6 Extending the RPC (`ecc rpc serve`) -`rpc serve --stdio` starts the JSON-RPC 2.0 sidecar (`chipcompiler/runtime/stdio_server.py`). Methods are declared in `chipcompiler/runtime/methods.py::RUNTIME_METHODS` (`method_name` + a pydantic `request_model` + `handler_name`), handler implementations live in `chipcompiler/runtime/workspace_api.py`, and `runtime/server.py` mounts them uniformly; protocol details in [workspace-cli.md](workspace-cli.md). Adding a method = one `RuntimeMethodSpec` + the matching API method + a request model; no CLI-layer changes needed. +`rpc serve --stdio` starts the JSON-RPC 2.0 sidecar (`chipcompiler/runtime/stdio_server.py`). Methods are declared in `chipcompiler/runtime/methods.py::RUNTIME_METHODS` (`method_name` + a pydantic `request_model` + `handler_name`), handler implementations live in `chipcompiler/runtime/workspace_api.py`, and `runtime/server.py` mounts them uniformly; protocol details in [workspace-cli.md](../../docs/workspace-cli.md). Adding a method = one `RuntimeMethodSpec` + the matching API method + a request model; no CLI-layer changes needed. ### 5.7 Extending project declarations (`ecc project *` / `ecc workspace refresh`) @@ -250,14 +250,14 @@ rm -rf ~/.local/ecc && mkdir -p ~/.local/ecc && cp -a dist/ecc/. ~/.local/ecc/ ecc --help # verify doctor / signoff / report are listed ``` -To roll back to the official release, re-run the [README](../README.md#installation) installer (`curl -fsSL http://release.openecos.com/installers/ecc/latest/ecc-installer.sh | sh`). +To roll back to the official release, re-run the [README](../../README.md#installation) installer (`curl -fsSL http://release.openecos.com/installers/ecc/latest/ecc-installer.sh | sh`). ## 7. Constraints and caveats (from the repository conventions) - **Module size**: once a file exceeds roughly 800 LoC, put new functionality in a new module instead of growing it (repository CLAUDE.md section 6). - **Python 3+**: do not use `__future__`; check `requires-python` in `pyproject.toml` for the minimum version. - **Test placement** follows ownership boundaries; prefer whole-object comparisons; do not write tests for statically defined values; do not keep negative tests for removed logic. -- **Code review** must enforce the additional standards in [review-guidelines.md](review-guidelines.md). +- **Code review** must enforce the additional standards in [review-guidelines.md](../../docs/review-guidelines.md). - `uv.lock` is the source of truth for dependencies; `requirements_lock.txt` is auto-generated and gitignored. - ECC-Tools' tool identifier in code is `"ecc"` (not `"ecc-tools"`); every tool module must implement `is_eda_exist / build_step / run_step`; steps execute in `multiprocessing.Process` and state persists in `workspace.flow.json`. - After installing dependencies, `ecc` is editable — source changes take effect on the next import, no reinstall needed. diff --git a/docs/ecc-cli-tutorial.cn.md b/chipcompiler/docs/ecc-cli-tutorial.cn.md similarity index 97% rename from docs/ecc-cli-tutorial.cn.md rename to chipcompiler/docs/ecc-cli-tutorial.cn.md index 56f52f24c..572dadfd5 100644 --- a/docs/ecc-cli-tutorial.cn.md +++ b/chipcompiler/docs/ecc-cli-tutorial.cn.md @@ -1,6 +1,6 @@ # ECC CLI 入门教程:从零跑通 RTL → Harden 并产出签核包 -本教程面向第一次接触 ECC 的用户:从一台只有 Linux 系统的机器开始,安装 `ecc` 命令行工具,把一个 Verilog RTL 设计([gcd](examples/gcd/gcd.v),最大公约数计算单元)一路跑完 **综合 → 布局布线 → 物理验证 → 逻辑等价性检查(LEC)→ 时序签核 → Harden** 全流程,最终拿到: +本教程面向第一次接触 ECC 的用户:从一台只有 Linux 系统的机器开始,安装 `ecc` 命令行工具,把一个 Verilog RTL 设计([gcd](../../docs/examples/gcd/gcd.v),最大公约数计算单元)一路跑完 **综合 → 布局布线 → 物理验证 → 逻辑等价性检查(LEC)→ 时序签核 → Harden** 全流程,最终拿到: - **Harden 交付物**:GDS 版图、抽象 LEF、时序 LIB、版图快照 PNG; - **签核包** `gcd_signoff_package.tar.gz`(含 RTL/配置/交付物/LEC 证明/报告等 300+ 文件); @@ -59,7 +59,7 @@ export PATH="$HOME/.local/bin:$PATH" ### 2.2 从源码运行(可选) -按 [README](../README.cn.md#源码构建) 带 `--recursive` 克隆仓库(`chipcompiler/thirdparty/` 会拉取 `ecc-tools` 和 `ecc-dreamplace`),再参照 [开发指南](development.md) 配置 `uv` 工作区: +按 [README](../../README.cn.md#源码构建) 带 `--recursive` 克隆仓库(`chipcompiler/thirdparty/` 会拉取 `ecc-tools` 和 `ecc-dreamplace`),再参照 [开发指南](../../docs/development.md) 配置 `uv` 工作区: ```bash git clone --recursive https://github.com/openecos-projects/ecc.git @@ -184,7 +184,7 @@ curl -fL -o rtl/gcd.v \ # cp /path/to/ecc/docs/examples/gcd/gcd.v rtl/ ``` -多文件设计请改用 filelist(`rtl = ["rtl/filelist.f"]`),语法见 [examples/gcd/README.md](examples/gcd/README.md#using-filelist) 与 [filelist 语法](specification/filelist-grammar.md)。 +多文件设计请改用 filelist(`rtl = ["rtl/filelist.f"]`),语法见 [examples/gcd/README.md](../../docs/examples/gcd/README.md#using-filelist) 与 [filelist 语法](../../docs/specification/filelist-grammar.md)。 ### 3.3 认识 ecc.toml @@ -713,10 +713,10 @@ ecc config --plain # 项目级配置(键值 + 解析后绝对路径) ## 8. 下一步 -- 换你自己的设计:改 `ecc.toml` 的 `top`/`rtl`/`clock_port`/`frequency_mhz`,多文件用 [filelist](examples/gcd/README.md#using-filelist); +- 换你自己的设计:改 `ecc.toml` 的 `top`/`rtl`/`clock_port`/`frequency_mhz`,多文件用 [filelist](../../docs/examples/gcd/README.md#using-filelist); - 了解 preset 差异:`rtl2gds`(完整 15 步综合到 Harden 链,含综合级 LEC)、`syn_sta`(仅综合)、`synthesis_lec`(综合 + LEC,两步); - 全部命令细节见 **[ECC CLI 用户指南](ecc-cli-ug.cn.md)**;CLI 扩展开发见 [ecc-cli-dev.cn.md](ecc-cli-dev.cn.md); -- 用 Python API 直接编排 flow(`EngineFlow`)见 [examples/gcd/ics55flow.py](examples/gcd/ics55flow.py)。 +- 用 Python API 直接编排 flow(`EngineFlow`)见 [examples/gcd/ics55flow.py](../../docs/examples/gcd/ics55flow.py)。 --- diff --git a/docs/ecc-cli-tutorial.en.md b/chipcompiler/docs/ecc-cli-tutorial.en.md similarity index 97% rename from docs/ecc-cli-tutorial.en.md rename to chipcompiler/docs/ecc-cli-tutorial.en.md index 4b170c0d6..4f7568760 100644 --- a/docs/ecc-cli-tutorial.en.md +++ b/chipcompiler/docs/ecc-cli-tutorial.en.md @@ -1,6 +1,6 @@ # ECC CLI Tutorial: From Zero to RTL → Harden with a Signoff Package -This tutorial is for first-time ECC users: starting from a bare Linux machine, install the `ecc` command-line tool and drive a Verilog RTL design ([gcd](examples/gcd/gcd.v), a greatest-common-divisor unit) through the full **synthesis → place & route → physical verification → logic equivalence check (LEC) → timing signoff → Harden** flow, ending up with: +This tutorial is for first-time ECC users: starting from a bare Linux machine, install the `ecc` command-line tool and drive a Verilog RTL design ([gcd](../../docs/examples/gcd/gcd.v), a greatest-common-divisor unit) through the full **synthesis → place & route → physical verification → logic equivalence check (LEC) → timing signoff → Harden** flow, ending up with: - **Harden deliverables**: GDS layout, abstract LEF, timing LIB, and a layout snapshot PNG; - A **signoff package** `gcd_signoff_package.tar.gz` (300+ files: RTL / configs / deliverables / LEC proof / reports); @@ -59,7 +59,7 @@ The `--with-toolchain` wrapper exports `CHIPCOMPILER_OSS_CAD_DIR` and `CHIPCOMPI ### 2.2 Running from source (optional) -Clone the repository with `--recursive` as the [README](../README.md#build-from-source) describes (`chipcompiler/thirdparty/` pulls in `ecc-tools` and `ecc-dreamplace`), then set up the `uv` workspace per the [development guide](development.md): +Clone the repository with `--recursive` as the [README](../../README.md#build-from-source) describes (`chipcompiler/thirdparty/` pulls in `ecc-tools` and `ecc-dreamplace`), then set up the `uv` workspace per the [development guide](../../docs/development.md): ```bash git clone --recursive https://github.com/openecos-projects/ecc.git @@ -185,7 +185,7 @@ curl -fL -o rtl/gcd.v \ # cp /path/to/ecc/docs/examples/gcd/gcd.v rtl/ ``` -For multi-file designs, switch to a filelist (`rtl = ["rtl/filelist.f"]`); see [examples/gcd/README.md](examples/gcd/README.md#using-filelist) and the [filelist grammar](specification/filelist-grammar.md). +For multi-file designs, switch to a filelist (`rtl = ["rtl/filelist.f"]`); see [examples/gcd/README.md](../../docs/examples/gcd/README.md#using-filelist) and the [filelist grammar](../../docs/specification/filelist-grammar.md). ### 3.3 Understanding ecc.toml @@ -714,10 +714,10 @@ ecc config --plain # project-level config (key=value + resolved absolute pa ## 8. Next Steps -- Try your own design: edit `top`/`rtl`/`clock_port`/`frequency_mhz` in `ecc.toml`; use a [filelist](examples/gcd/README.md#using-filelist) for multi-file designs; +- Try your own design: edit `top`/`rtl`/`clock_port`/`frequency_mhz` in `ecc.toml`; use a [filelist](../../docs/examples/gcd/README.md#using-filelist) for multi-file designs; - Preset differences: `rtl2gds` (the complete 15-step synthesis-to-Harden chain, including synthesis-level LEC), `syn_sta` (synthesis only), and `synthesis_lec` (synthesis + LEC, two steps); - Full command details in the **[ECC CLI User Guide](ecc-cli-ug.en.md)**; extending the CLI is covered in [ecc-cli-dev.en.md](ecc-cli-dev.en.md); -- Driving the flow directly via the Python API (`EngineFlow`): [examples/gcd/ics55flow.py](examples/gcd/ics55flow.py). +- Driving the flow directly via the Python API (`EngineFlow`): [examples/gcd/ics55flow.py](../../docs/examples/gcd/ics55flow.py). --- diff --git a/docs/ecc-cli-ug.cn.md b/chipcompiler/docs/ecc-cli-ug.cn.md similarity index 96% rename from docs/ecc-cli-ug.cn.md rename to chipcompiler/docs/ecc-cli-ug.cn.md index 77b14b849..69c8e11d2 100644 --- a/docs/ecc-cli-ug.cn.md +++ b/chipcompiler/docs/ecc-cli-ug.cn.md @@ -2,9 +2,9 @@ `ecc` 是 ECOS Chip Compiler 的项目制命令行入口,覆盖 RTL-to-GDS 流水的建项、校验、运行、状态/日志/配置查询、参数管理、签核与报告。本文基于 `ecc/` 子模块当前源码(v0.1.0-alpha.11)整理,所有示例输出均为真实执行结果(示例中的 run 状态为手工构造的演示数据)。 -- 源码位置:[chipcompiler/cli/](../chipcompiler/cli/) +- 源码位置:[chipcompiler/cli/](../../chipcompiler/cli/) - 命令扩展开发方式见同目录 [ecc-cli-dev.cn.md](ecc-cli-dev.cn.md) -- RPC sidecar 协议详见 [workspace-cli.md](workspace-cli.md) +- RPC sidecar 协议详见 [workspace-cli.md](../../docs/workspace-cli.md) ## 0. 调用方式 @@ -104,6 +104,7 @@ Commands: config Show resolved project or step configuration migrate Migrate a legacy runs/ project to the manifest layout doctor Check host environment: PDK, tools, and components + doc Show a bundled guide (config/ug/tutorial/dev) in the terminal param Manage EDA parameters pdk Show and configure the PDK path used by this project project Edit project declarations in ecc.toml @@ -113,6 +114,32 @@ Commands: rpc Run the private ECC JSON-RPC runtime ``` +## 1.5. doc — 在终端阅读内置指南 + +`ecc doc` 将随包内置的 CLI 指南直接渲染到终端,因此在打包安装(无源码、无文档目录)的环境中也能离线查阅完整参考。 + +```bash +ecc doc config # 完整配置参考(渲染输出) +ecc doc ug --lang cn # 本指南的中文版 +ecc doc config --plain # 原始 markdown,逐字节输出 +``` + +- 主题:`config`、`ug`、`tutorial`、`dev`;`--lang` 选择 `en`(默认)或 `cn`。 +- 终端下渲染输出带高亮并进入分页器翻阅(`$PAGER`,回退到 `less`/`more`;未设置 `LESS` 时默认 `LESS=FRX`,保证 `less` 下颜色生效);管道场景全量直出、不带颜色。 +- 非法的主题/语言取值由参数校验拒绝(退出码 2)。 +- 管道输出保留 unicode 渲染版式;`--plain` 原样输出原始 markdown,适合脚本处理。 + +## 1.6. Shell 补全 + +`ecc` 内置 bash、zsh、fish、powershell 的 Shell 补全。打印激活脚本并加载到当前会话: + +```bash +eval "$(ecc --show-completion)" # 自动探测当前 shell +``` + +- 把该行写入 `~/.zshrc` / `~/.bashrc`(或 home-manager 的 `initExtra`)即可永久启用。zsh 需要先初始化 `compinit`。 +- 在 NixOS 等声明式管理的环境中,请用上面的 `eval` 方式而不是 `--install-completion`:后者会原地改写 `~/.zshrc` / `~/.bashrc`,对只读 rc 符号链接会失败,且与 home-manager 冲突。 + ## 2. version — 查看版本 ```bash @@ -944,7 +971,7 @@ $ ecc report step drc --section analysis ecc rpc serve --stdio [--persistent-db] ``` -供 GUI 等前端使用的 JSON-RPC 2.0 服务,`Content-Length` 帧封装于 stdio。`--persistent-db` 额外开放 `db.ensure` / `db.release` 与 `layout.edit.*` / `floorplan.edit.*` 系列方法。握手与调用示例(完整方法列表和参数见 [workspace-cli.md](workspace-cli.md)): +供 GUI 等前端使用的 JSON-RPC 2.0 服务,`Content-Length` 帧封装于 stdio。`--persistent-db` 额外开放 `db.ensure` / `db.release` 与 `layout.edit.*` / `floorplan.edit.*` 系列方法。握手与调用示例(完整方法列表和参数见 [workspace-cli.md](../../docs/workspace-cli.md)): ```console → {"jsonrpc":"2.0","method":"rpc.hello","params":{"version":1},"id":"hello-1"} diff --git a/docs/ecc-cli-ug.en.md b/chipcompiler/docs/ecc-cli-ug.en.md similarity index 96% rename from docs/ecc-cli-ug.en.md rename to chipcompiler/docs/ecc-cli-ug.en.md index 1c866a52a..660df3c50 100644 --- a/docs/ecc-cli-ug.en.md +++ b/chipcompiler/docs/ecc-cli-ug.en.md @@ -2,9 +2,9 @@ `ecc` is the project-oriented command-line entry point of ECOS Chip Compiler, covering the full RTL-to-GDS flow: project creation, validation, execution, status/log/config inspection, parameter management, signoff, and reporting. This guide is based on the current source tree (v0.1.0-alpha.11); all example outputs are real execution results (run states in the examples are hand-crafted demo data). -- Source code: [chipcompiler/cli/](../chipcompiler/cli/) +- Source code: [chipcompiler/cli/](../../chipcompiler/cli/) - For how to extend the CLI with new commands, see [ecc-cli-dev.en.md](ecc-cli-dev.en.md) -- RPC sidecar protocol: [workspace-cli.md](workspace-cli.md) +- RPC sidecar protocol: [workspace-cli.md](../../docs/workspace-cli.md) ## 0. Invocation @@ -104,6 +104,7 @@ Commands: config Show resolved project or step configuration migrate Migrate a legacy runs/ project to the manifest layout doctor Check host environment: PDK, tools, and components + doc Show a bundled guide (config/ug/tutorial/dev) in the terminal param Manage EDA parameters pdk Show and configure the PDK path used by this project project Edit project declarations in ecc.toml @@ -113,6 +114,32 @@ Commands: rpc Run the private ECC JSON-RPC runtime ``` +## 1.5. doc — read the bundled guides in the terminal + +`ecc doc` renders the bundled CLI guides directly in the terminal, so the full reference stays available offline inside packaged installations. + +```bash +ecc doc config # full configuration reference (rendered) +ecc doc ug --lang cn # this guide, Chinese edition +ecc doc config --plain # raw markdown, byte-for-byte +``` + +- Topics: `config`, `ug`, `tutorial`, `dev`; `--lang` selects `en` (default) or `cn`. +- On a terminal the rendered guide opens in a pager with highlighting (`$PAGER`, falling back to `less`/`more`; `LESS=FRX` is defaulted when unset so colors survive `less`). When piped it prints in full without colors. +- Invalid topic/language values are rejected by argument validation (exit 2). +- Default output keeps the rendered unicode layout even when piped; `--plain` prints the raw markdown unchanged (script-friendly). + +## 1.6. Shell completion + +`ecc` ships built-in shell completion for bash, zsh, fish, and powershell. Print the activation script and load it into the current session: + +```bash +eval "$(ecc --show-completion)" # auto-detects the current shell +``` + +- Add that line to `~/.zshrc` / `~/.bashrc` (or home-manager `initExtra`) to enable completion permanently. zsh requires `compinit` to be initialized first. +- On NixOS or other declaratively managed setups, prefer the `eval` line over `--install-completion`: the latter rewrites `~/.zshrc` / `~/.bashrc` in place, which fails on read-only rc symlinks and conflicts with home-manager. + ## 2. version — show versions ```bash @@ -992,7 +1019,7 @@ $ ecc report step drc --section analysis ecc rpc serve --stdio [--persistent-db] ``` -A JSON-RPC 2.0 service for front ends such as the GUI, framed with `Content-Length` over stdio. `--persistent-db` additionally exposes `db.ensure` / `db.release` plus the `layout.edit.*` / `floorplan.edit.*` method families. Handshake and call examples (full method list and parameters in [workspace-cli.md](workspace-cli.md)): +A JSON-RPC 2.0 service for front ends such as the GUI, framed with `Content-Length` over stdio. `--persistent-db` additionally exposes `db.ensure` / `db.release` plus the `layout.edit.*` / `floorplan.edit.*` method families. Handshake and call examples (full method list and parameters in [workspace-cli.md](../../docs/workspace-cli.md)): ```console → {"jsonrpc":"2.0","method":"rpc.hello","params":{"version":1},"id":"hello-1"} diff --git a/chipcompiler/tools/ecc/__init__.py b/chipcompiler/tools/ecc/__init__.py index 747ab34a8..9b59a1372 100644 --- a/chipcompiler/tools/ecc/__init__.py +++ b/chipcompiler/tools/ecc/__init__.py @@ -1,13 +1,33 @@ +from typing import TYPE_CHECKING + from .builder import build_step, build_step_config, build_step_space from .checklist import EccChecklist from .metrics import build_step_metrics from .module import ECCToolsModule -from .plot import ECCToolsPlot from .runner import create_db_engine, run_step from .service import get_step_info from .subflow import EccSubFlow, EccSubFlowEnum from .utility import is_eda_exist +# ECCToolsPlot pulls in matplotlib through the utility plot helpers, so it is +# re-exported lazily via PEP 562 instead of at package import time. +_PLOT_EXPORTS = frozenset({"ECCToolsPlot"}) + +if TYPE_CHECKING: + from .plot import ECCToolsPlot +else: + # Hidden from type checkers so unknown attributes still fail statically. + def __getattr__(name: str): + if name in _PLOT_EXPORTS: + from . import plot + + return getattr(plot, name) + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + + def __dir__() -> list[str]: + return sorted(set(globals()) | _PLOT_EXPORTS) + + __all__ = [ "is_eda_exist", "build_default_flow", diff --git a/chipcompiler/tools/ecc/runner.py b/chipcompiler/tools/ecc/runner.py index a75e21792..08e61be5b 100644 --- a/chipcompiler/tools/ecc/runner.py +++ b/chipcompiler/tools/ecc/runner.py @@ -18,7 +18,6 @@ save_rcx_spef_feature_facts, ) from chipcompiler.tools.ecc.module import ECCToolsModule -from chipcompiler.tools.ecc.plot import ECCToolsPlot from chipcompiler.tools.ecc.sta_artifacts import discard_sta_outputs from chipcompiler.tools.ecc.sta_qor import ( POST_SYNTHESIS_STA_CORNER, @@ -534,6 +533,8 @@ def run_analysis(workspace: Workspace, step: EccStep, subflow: EccSubFlow): build_step_metrics(workspace=workspace, step=step, subflow=subflow) # plot layout image + from chipcompiler.tools.ecc.plot import ECCToolsPlot + ploter = ECCToolsPlot(workspace=workspace, step=step) ploter.plot() diff --git a/chipcompiler/utility/__init__.py b/chipcompiler/utility/__init__.py index 3f7a3b88a..8f0b288ed 100644 --- a/chipcompiler/utility/__init__.py +++ b/chipcompiler/utility/__init__.py @@ -1,3 +1,5 @@ +from typing import TYPE_CHECKING + from .csv import csv_write from .file import chmod_folder, file_digest, find_files from .filelist import ( @@ -16,9 +18,37 @@ redirect_stdio_to_file, rotate_log_on_start, ) -from .plot import plot_bar_chart, plot_csv_bar_chart, plot_csv_map, plot_csv_table, plot_metrics from .util import track_process_memory +# Plot helpers pull in matplotlib (~1s import, font scan on cold cache), so +# they are re-exported lazily via PEP 562 instead of at package import time. +_PLOT_EXPORTS = frozenset( + {"plot_bar_chart", "plot_csv_bar_chart", "plot_csv_map", "plot_csv_table", "plot_metrics"} +) + +if TYPE_CHECKING: + from .plot import ( + plot_bar_chart, + plot_csv_bar_chart, + plot_csv_map, + plot_csv_table, + plot_metrics, + ) + + +else: + # Hidden from type checkers so unknown attributes still fail statically. + def __getattr__(name: str): + if name in _PLOT_EXPORTS: + from . import plot + + return getattr(plot, name) + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + + def __dir__() -> list[str]: + return sorted(set(globals()) | _PLOT_EXPORTS) + + __all__ = [ "chmod_folder", "json_read", diff --git a/docs/index.md b/docs/index.md index c25a51ee9..a53387d2e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -6,15 +6,15 @@ Welcome to the ChipCompiler documentation center. The `ecc` command-line tool ships bilingual guides (`.en.md` / `.cn.md`): -- **[CLI Tutorial](ecc-cli-tutorial.en.md)** / **[中文教程](ecc-cli-tutorial.cn.md)** - From zero to RTL → Harden with a signoff package +- **[CLI Tutorial](../chipcompiler/docs/ecc-cli-tutorial.en.md)** / **[中文教程](../chipcompiler/docs/ecc-cli-tutorial.cn.md)** - From zero to RTL → Harden with a signoff package - Installing the ecc CLI, PDK, and Yosys - First project, the 15-step `rtl2gds` flow, signoff package, and reports - Tuning parameters, workspaces, and rerun scenarios -- **[CLI User Guide](ecc-cli-ug.en.md)** / **[中文用户指南](ecc-cli-ug.cn.md)** - All currently supported commands +- **[CLI User Guide](../chipcompiler/docs/ecc-cli-ug.en.md)** / **[中文用户指南](../chipcompiler/docs/ecc-cli-ug.cn.md)** - All currently supported commands - Every command and option: `init`/`check`/`run`/`status`/`log`/`config`/`doctor`/`param`/`pdk`/`project`/`workspace`/`signoff`/`report`/`rpc`/`layout-image` - Run selectors (`--resume`/`--from`/`--to`/`--only`), error-code reference, end-to-end workflows -- **[CLI Config Reference](ecc-cli-config.en.md)** / **[中文配置参考](ecc-cli-config.cn.md)** - `ecc.toml`, workspace files, and the parameter system -- **[CLI Dev Guide](ecc-cli-dev.en.md)** / **[中文开发指南](ecc-cli-dev.cn.md)** - Adding or modifying CLI commands +- **[CLI Config Reference](../chipcompiler/docs/ecc-cli-config.en.md)** / **[中文配置参考](../chipcompiler/docs/ecc-cli-config.cn.md)** - `ecc.toml`, workspace files, and the parameter system +- **[CLI Dev Guide](../chipcompiler/docs/ecc-cli-dev.en.md)** / **[中文开发指南](../chipcompiler/docs/ecc-cli-dev.cn.md)** - Adding or modifying CLI commands - **[Workspace CLI Guide](workspace-cli.md)** - Private JSON-RPC runtime sidecar protocol (`ecc rpc serve`) ## Core Documentation @@ -58,10 +58,10 @@ ChipCompiler supports various EDA file formats. Technical specifications for par ### I want to... - **Get started with ChipCompiler** → See main [README](../README.md) -- **Run my first RTL-to-GDS flow** → [CLI Tutorial](ecc-cli-tutorial.en.md) / [中文教程](ecc-cli-tutorial.cn.md) -- **Look up an `ecc` command or option** → [CLI User Guide](ecc-cli-ug.en.md) / [中文用户指南](ecc-cli-ug.cn.md) -- **Understand `ecc.toml` / workspace files / parameters** → [CLI Config Reference](ecc-cli-config.en.md) / [中文配置参考](ecc-cli-config.cn.md) -- **Extend the CLI with new commands** → [CLI Dev Guide](ecc-cli-dev.en.md) +- **Run my first RTL-to-GDS flow** → [CLI Tutorial](../chipcompiler/docs/ecc-cli-tutorial.en.md) / [中文教程](../chipcompiler/docs/ecc-cli-tutorial.cn.md) +- **Look up an `ecc` command or option** → [CLI User Guide](../chipcompiler/docs/ecc-cli-ug.en.md) / [中文用户指南](../chipcompiler/docs/ecc-cli-ug.cn.md) +- **Understand `ecc.toml` / workspace files / parameters** → [CLI Config Reference](../chipcompiler/docs/ecc-cli-config.en.md) / [中文配置参考](../chipcompiler/docs/ecc-cli-config.cn.md) +- **Extend the CLI with new commands** → [CLI Dev Guide](../chipcompiler/docs/ecc-cli-dev.en.md) - **Use legacy workspace commands** → [Workspace CLI Guide](workspace-cli.md) - **Understand the architecture** → [Architecture](architecture.md) - **Set up development environment** → [Development Guide](development.md) diff --git a/ecc.spec b/ecc.spec index 2f530cb44..528444ce0 100644 --- a/ecc.spec +++ b/ecc.spec @@ -52,6 +52,17 @@ DREAMPLACE_THIRDPARTY_FILES = ( "thirdparty/NCTUgr.ICCAD2012/ICCAD12.set", ) +DOC_GUIDES = ( + "chipcompiler/docs/ecc-cli-config.en.md", + "chipcompiler/docs/ecc-cli-config.cn.md", + "chipcompiler/docs/ecc-cli-ug.en.md", + "chipcompiler/docs/ecc-cli-ug.cn.md", + "chipcompiler/docs/ecc-cli-tutorial.en.md", + "chipcompiler/docs/ecc-cli-tutorial.cn.md", + "chipcompiler/docs/ecc-cli-dev.en.md", + "chipcompiler/docs/ecc-cli-dev.cn.md", +) + LINUX_RUNTIME_LIBS = ( "/lib/x86_64-linux-gnu/libgomp.so.1", "/lib/x86_64-linux-gnu/libtbb.so.12", @@ -174,6 +185,20 @@ def collect_dreamplace_thirdparty_files(): return datas +def collect_doc_guides(): + datas = [] + for relpath in DOC_GUIDES: + src = ECC_DIR / relpath + if src.is_file(): + datas.append((str(src), "docs")) + else: + warnings.warn( + f"Required ECC runtime resource was not collected: {relpath}", + stacklevel=2, + ) + return datas + + def collect_platform_runtime_libs(): if sys.platform.startswith("linux"): binaries = [] @@ -205,6 +230,40 @@ def collect_ecc_tools_extension_binaries(): ) +def filter_host_fontconfig(binaries): + # PyInstaller pulls libfontconfig in as a DT_NEEDED dependency of the + # bundled libcairo. Do not ship it: a bundled library parses the host's + # /etc/fonts/conf.d, and version skew against those host configs spams + # Fontconfig warnings (and breaks the host fc-list binary with symbol + # errors when the bundle lib dir is on its library search path). The + # host libfontconfig always matches the host fontconfig data. Applied + # to the Analysis output, because input-list filtering cannot stop the + # dependency walk from re-collecting it. + return [ + entry + for entry in binaries + if not any(Path(part).name.startswith("libfontconfig.so") for part in entry[:2]) + ] + + +def rich_unicode_data_hiddenimports(): + # rich imports its per-Unicode-version cell-width tables dynamically + # (rich._unicode_data.unicode--), so PyInstaller's static + # analysis cannot see them; render-only CJK output needs them at runtime. + module_spec = find_spec("rich") + if module_spec is None or module_spec.submodule_search_locations is None: + return [] + + names = [] + for package_root in module_spec.submodule_search_locations: + data_dir = Path(package_root) / "_unicode_data" + if data_dir.is_dir(): + names.extend( + f"rich._unicode_data.{path.stem}" for path in sorted(data_dir.glob("unicode*.py")) + ) + return names + + ecc_datas, ecc_binaries, ecc_hiddenimports = collect_all("chipcompiler") ecc_tools_datas, ecc_tools_binaries, ecc_tools_hiddenimports = collect_all("ecc_tools_bin") klayout_datas, klayout_binaries, klayout_hiddenimports = collect_all("klayout") @@ -221,6 +280,7 @@ datas.extend(collect_required_metadata()) datas.extend(collect_ecc_resources()) datas.extend(collect_jsonrpcserver_resources()) datas.extend(collect_dreamplace_thirdparty_files()) +datas.extend(collect_doc_guides()) binaries = [] binaries.extend(ecc_binaries) @@ -234,6 +294,7 @@ binaries = filter_collected_payloads(binaries) hiddenimports = [] hiddenimports.extend(HIDDENIMPORTS) +hiddenimports.extend(rich_unicode_data_hiddenimports()) hiddenimports.extend(ecc_hiddenimports) hiddenimports.extend(ecc_tools_hiddenimports) hiddenimports.extend(klayout_hiddenimports) @@ -254,6 +315,8 @@ a = Analysis( noarchive=False, ) +a.binaries = filter_host_fontconfig(a.binaries) + pyz = PYZ(a.pure, a.zipped_data) if BUNDLE_MODE == "onedir": diff --git a/test/cli/conftest.py b/test/cli/conftest.py index 2a0d62486..fd84107b0 100644 --- a/test/cli/conftest.py +++ b/test/cli/conftest.py @@ -153,6 +153,11 @@ def _stub_run_preflight(monkeypatch): ) +@pytest.fixture(autouse=True) +def _disable_typer_terminal_forcing(monkeypatch): + monkeypatch.setattr("typer.rich_utils.FORCE_TERMINAL", False) + + @pytest.fixture(name="create_cli_project") def create_cli_project_fixture(tmp_path): def factory(name="gcd", pdk_root=None, freq=100.0): diff --git a/test/cli/test_cli_module_layout.py b/test/cli/test_cli_module_layout.py index 5339aa6e4..50b081547 100644 --- a/test/cli/test_cli_module_layout.py +++ b/test/cli/test_cli_module_layout.py @@ -1,3 +1,4 @@ +import ast import importlib from pathlib import Path @@ -173,3 +174,21 @@ def test_production_code_does_not_import_removed_inspection_modules(): source = source_path.read_text() for name in forbidden_imports: assert name not in source, source_path + + +def test_typer_apps_are_created_through_the_shared_factory(): + package_root = Path(__file__).parents[2] / "chipcompiler" / "cli" + factory = package_root / "core" / "apps.py" + + for source_path in package_root.rglob("*.py"): + if source_path == factory: + continue + tree = ast.parse(source_path.read_text()) + for node in ast.walk(tree): + if not isinstance(node, ast.Call): + continue + func = node.func + if isinstance(func, ast.Name) and func.id == "Typer": + raise AssertionError(source_path) + if isinstance(func, ast.Attribute) and func.attr == "Typer": + raise AssertionError(source_path) diff --git a/test/cli/test_completion.py b/test/cli/test_completion.py new file mode 100644 index 000000000..ac981db09 --- /dev/null +++ b/test/cli/test_completion.py @@ -0,0 +1,45 @@ +from chipcompiler.cli import main as cli_main + + +def test_show_completion_prints_zsh_script(monkeypatch, capsys): + monkeypatch.setenv("_TYPER_COMPLETE_TEST_DISABLE_SHELL_DETECTION", "1") + + rc = cli_main.run(["--show-completion", "zsh"]) + + out = capsys.readouterr().out + assert rc == 0 + assert "#compdef ecc" in out + assert "_ECC_COMPLETE=complete_zsh" in out + + +def test_show_completion_prints_bash_script(monkeypatch, capsys): + monkeypatch.setenv("_TYPER_COMPLETE_TEST_DISABLE_SHELL_DETECTION", "1") + + rc = cli_main.run(["--show-completion", "bash"]) + + out = capsys.readouterr().out + assert rc == 0 + assert "complete -o default -F _ecc_completion ecc" in out + assert "_ECC_COMPLETE=complete_bash" in out + + +def test_completion_request_with_empty_argv_returns_candidates(monkeypatch, capsys): + monkeypatch.setenv("_ECC_COMPLETE", "complete_bash") + monkeypatch.setenv("COMP_WORDS", "ecc vers") + monkeypatch.setenv("COMP_CWORD", "1") + + rc = cli_main.run([]) + + out = capsys.readouterr().out + assert rc == 0 + assert out.strip() == "version" + + +def test_empty_argv_without_completion_env_still_shows_help(monkeypatch, capsys): + monkeypatch.delenv("_ECC_COMPLETE", raising=False) + + rc = cli_main.run([]) + + out = capsys.readouterr() + assert rc == 1 + assert "Commands" in out.out + out.err diff --git a/test/cli/test_doc.py b/test/cli/test_doc.py new file mode 100644 index 000000000..029061285 --- /dev/null +++ b/test/cli/test_doc.py @@ -0,0 +1,177 @@ +import sys +from pathlib import Path + +import pytest + +from chipcompiler.cli import main as cli_main +from chipcompiler.cli.core import docs +from chipcompiler.cli.core.docs import guides_root + + +def test_guides_root_points_at_repository_docs_in_dev_mode(): + repo_docs = Path(__file__).parents[2] / "chipcompiler" / "docs" + + assert guides_root() == repo_docs + assert (guides_root() / "ecc-cli-config.en.md").is_file() + + +def test_all_four_topics_resolve_in_both_languages(): + for topic in docs.GUIDE_STEMS: + for lang in ("en", "cn"): + text = docs.load_guide(topic, lang).decode("utf-8") + assert text.startswith("# ") + + +def test_ug_guide_documents_the_doc_command_in_both_languages(): + for lang in ("en", "cn"): + text = docs.load_guide("ug", lang).decode("utf-8") + assert "## 1.5. doc" in text + assert "ecc doc config" in text + + +def test_doc_config_plain_is_byte_identical_to_the_guide_file(capsysbinary): + rc = cli_main.run(["doc", "config", "--plain"]) + + out = capsysbinary.readouterr().out + assert rc == 0 + assert out == (guides_root() / "ecc-cli-config.en.md").read_bytes() + + +def test_doc_plain_preserves_crlf_line_endings(tmp_path, monkeypatch, capsysbinary): + guide = tmp_path / "docs" / "ecc-cli-config.en.md" + guide.parent.mkdir() + guide.write_bytes(b"# Packaged guide\r\n\r\ntext\r\n") + monkeypatch.setattr(sys, "_MEIPASS", str(tmp_path), raising=False) + + rc = cli_main.run(["doc", "config", "--plain"]) + + out = capsysbinary.readouterr().out + assert rc == 0 + assert out == b"# Packaged guide\r\n\r\ntext\r\n" + + +def test_rendered_output_survives_non_utf8_stdout_via_plain_fallback(tmp_path, monkeypatch, capsys): + import io + + guide = tmp_path / "docs" / "ecc-cli-config.en.md" + guide.parent.mkdir() + guide.write_bytes("# Packaged guide\n\ntext with ünïcode\n".encode()) + monkeypatch.setattr(sys, "_MEIPASS", str(tmp_path), raising=False) + ascii_stdout = io.TextIOWrapper(io.BytesIO(), encoding="ascii") + monkeypatch.setattr(sys, "stdout", ascii_stdout) + + rc = cli_main.run(["doc", "config", "--plain"]) + assert rc == 0 + + rc = cli_main.run(["doc", "config"]) + captured = capsys.readouterr() + assert rc == 1 + assert "Error:" in captured.err + + +def test_doc_uses_packaged_docs_when_frozen(tmp_path, monkeypatch, capsys): + guide = tmp_path / "docs" / "ecc-cli-config.en.md" + guide.parent.mkdir() + guide.write_text("# Packaged guide\n", encoding="utf-8") + monkeypatch.setattr(sys, "_MEIPASS", str(tmp_path), raising=False) + + rc = cli_main.run(["doc", "config", "--plain"]) + + out = capsys.readouterr().out + assert rc == 0 + assert out == "# Packaged guide\n" + + +def test_missing_guide_resource_fails_with_exit_1(tmp_path, monkeypatch, capsys): + monkeypatch.setattr(sys, "_MEIPASS", str(tmp_path), raising=False) + + rc = cli_main.run(["doc", "config", "--plain"]) + + captured = capsys.readouterr() + assert rc == 1 + assert "Error: doc resource not found: ecc-cli-config.en.md" in captured.err + + +def test_doc_chinese_language(capsys): + rc = cli_main.run(["doc", "config", "--lang", "cn", "--plain"]) + + out = capsys.readouterr().out + assert rc == 0 + assert any("一" <= ch <= "鿿" for ch in out) + + +def test_doc_default_text_output_keeps_unicode_layout_without_ansi(capsys): + rc = cli_main.run(["doc", "config"]) + + out = capsys.readouterr().out + assert rc == 0 + assert "\x1b[" not in out + assert "─" in out + + +def test_doc_pages_the_full_guide_when_stdout_is_a_tty(monkeypatch, capsys): + import pydoc + + paged = [] + monkeypatch.setattr(pydoc, "pager", paged.append) + monkeypatch.setattr(sys.stdout, "isatty", lambda: True) + monkeypatch.setattr("chipcompiler.cli.commands.doc.supports_color", lambda: False) + + rc = cli_main.run(["doc", "config"]) + + assert rc == 0 + assert capsys.readouterr().out == "" + assert len(paged) == 1 + assert "cts_ecc.json" in paged[0] + assert "\x1b[" not in paged[0] + + +def test_doc_pager_keeps_styles_when_color_is_supported(monkeypatch, capsys): + import pydoc + + paged = [] + monkeypatch.setattr(pydoc, "pager", paged.append) + monkeypatch.setattr(sys.stdout, "isatty", lambda: True) + monkeypatch.setattr("chipcompiler.cli.commands.doc.supports_color", lambda: True) + + rc = cli_main.run(["doc", "config"]) + + assert rc == 0 + assert capsys.readouterr().out == "" + assert len(paged) == 1 + assert "\x1b[" in paged[0] + + +def test_doc_pager_defaults_less_and_restores_the_environment(monkeypatch, capsys): + import os + import pydoc + + seen = [] + monkeypatch.setattr(pydoc, "pager", lambda text: seen.append(os.environ.get("LESS"))) + monkeypatch.setattr(sys.stdout, "isatty", lambda: True) + monkeypatch.delenv("LESS", raising=False) + + rc = cli_main.run(["doc", "config"]) + + assert rc == 0 + assert seen == ["FRX"] + assert "LESS" not in os.environ + + +@pytest.mark.parametrize( + "argv", + [ + ["doc", "bogus"], + ["doc", "CONFIG"], + ["doc"], + ["doc", "config", "--lang", "jp"], + ["doc", "config", "7"], + ], + ids=lambda argv: " ".join(argv), +) +def test_doc_invalid_arguments_exit_2(argv, capsys): + rc = cli_main.run(argv) + + captured = capsys.readouterr() + assert rc == 2 + assert captured.err diff --git a/test/cli/test_help_rendering.py b/test/cli/test_help_rendering.py new file mode 100644 index 000000000..c8fc586ea --- /dev/null +++ b/test/cli/test_help_rendering.py @@ -0,0 +1,166 @@ +import pytest +import typer + +from chipcompiler.cli import main as cli_main +from chipcompiler.cli.app import app + + +def _walk(command, path): + yield path + for name, sub in sorted(getattr(command, "commands", {}).items()): + yield from _walk(sub, [*path, name]) + + +def _all_command_paths(): + return list(_walk(typer.main.get_command(app), [])) + + +@pytest.mark.parametrize("path", _all_command_paths(), ids=lambda path: " ".join(path) or "ecc") +def test_help_renders_for_every_command(path, capsys): + rc = cli_main.run([*path, "--help"]) + + captured = capsys.readouterr() + assert rc == 0 + assert captured.out + assert not captured.err + assert "\x1b[" not in captured.out + + +def test_help_keeps_styles_when_color_is_forced(monkeypatch, capsys): + monkeypatch.setattr("typer.rich_utils.FORCE_TERMINAL", True) + + rc = cli_main.run(["--help"]) + + captured = capsys.readouterr() + assert rc == 0 + assert "\x1b[" in captured.out + assert not captured.err + + +def test_root_command_list_keeps_one_line_summaries(capsys): + rc = cli_main.run(["--help"]) + + out = capsys.readouterr().out + assert rc == 0 + assert "Run the configured RTL-to-GDS flow" in out + assert "Show resolved project or step configuration" in out + + +def test_param_command_list_keeps_one_line_summaries(capsys): + rc = cli_main.run(["param", "--help"]) + + out = capsys.readouterr().out + assert rc == 0 + assert "List parameter overrides" in out + assert "Show one parameter value" in out + assert "Set a parameter override" in out + assert "Remove a parameter override" in out + assert "Compare parameter overrides with defaults" in out + + +def test_workspace_refresh_summary_unchanged(capsys): + rc = cli_main.run(["workspace", "--help"]) + + out = capsys.readouterr().out + assert rc == 0 + assert "Recreate a workspace from ecc.toml without running it" in out + + +def test_pdk_set_root_summary_unchanged(capsys): + rc = cli_main.run(["pdk", "--help"]) + + out = capsys.readouterr().out + assert rc == 0 + assert "Set the [pdk] root path in ecc.toml" in out + + +def test_option_help_preserves_angle_bracket_placeholder(capsys): + rc = cli_main.run(["report", "qor", "--help"]) + + out = capsys.readouterr().out + assert rc == 0 + assert "/signoff/" in out + + +def test_param_set_help_documents_scopes_and_value_parsing(capsys): + rc = cli_main.run(["param", "set", "--help"]) + + out = capsys.readouterr().out + assert rc == 0 + assert "ecc.toml" in out + assert "params.toml" in out + assert "'[4, 5]'" in out + assert "skew_bound" in out + assert "max_buf_tran" in out + assert "invalid_value" in out + + +def test_param_set_help_wraps_at_narrow_width(capsys, monkeypatch): + monkeypatch.setenv("COLUMNS", "50") + + rc = cli_main.run(["param", "set", "--help"]) + + out = capsys.readouterr().out + assert rc == 0 + assert "…" not in out + for token in ("pdk.overrides", "params.toml", "workspace NAME"): + assert token in out + + +def test_run_help_documents_fresh_run_override_rule(capsys): + rc = cli_main.run(["run", "--help"]) + + out = capsys.readouterr().out + assert rc == 0 + assert "set_requires_fresh_run" in out + assert "cli-param-overrides.json" in out + + +def test_workspace_refresh_help_warns_about_manual_edits(capsys): + rc = cli_main.run(["workspace", "refresh", "--help"]) + + out = capsys.readouterr().out + assert rc == 0 + assert "overwritten" in out + + +def test_config_help_names_steps_without_step_specific_config(capsys): + rc = cli_main.run(["config", "--help"]) + + out = capsys.readouterr().out + assert rc == 0 + for step in ("lec", "lvs", "postroutelec", "harden"): + assert step in out + + +def test_pdk_set_root_help_documents_path_resolution(capsys): + rc = cli_main.run(["pdk", "set-root", "--help"]) + + out = capsys.readouterr().out + assert rc == 0 + assert "pdk.sdc" in out + assert "pdk.spef" in out + assert "project directory" in out + + +@pytest.mark.parametrize( + "path", + [ + ["param", "list"], + ["param", "show"], + ["param", "set"], + ["param", "unset"], + ["param", "diff"], + ["run"], + ["config"], + ["workspace", "refresh"], + ["pdk", "set-root"], + ], + ids=lambda path: " ".join(path), +) +def test_enriched_help_points_to_doc_config(path, capsys): + rc = cli_main.run([*path, "--help"]) + + out = capsys.readouterr().out + assert rc == 0 + assert "ecc doc config" in out diff --git a/test/packaging/test_cli_entrypoint.py b/test/packaging/test_cli_entrypoint.py index 6da80f699..36251e45a 100644 --- a/test/packaging/test_cli_entrypoint.py +++ b/test/packaging/test_cli_entrypoint.py @@ -34,3 +34,24 @@ def test_pyinstaller_spec_filters_payloads_before_analysis(self): assert datas_filter_index < analysis_index assert binaries_filter_index < analysis_index + + def test_pyinstaller_spec_collects_doc_guides(self): + project_root = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) + spec_path = os.path.join(project_root, "ecc.spec") + + with open(spec_path, encoding="utf-8") as f: + source = f.read() + + assert "datas.extend(collect_doc_guides())" in source + for stem in ("config", "ug", "tutorial", "dev"): + for lang in ("en", "cn"): + assert f"chipcompiler/docs/ecc-cli-{stem}.{lang}.md" in source + + def test_pyinstaller_spec_collects_rich_unicode_data_modules(self): + project_root = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) + spec_path = os.path.join(project_root, "ecc.spec") + + with open(spec_path, encoding="utf-8") as f: + source = f.read() + + assert "hiddenimports.extend(rich_unicode_data_hiddenimports())" in source diff --git a/test/packaging/test_wheel_contents.py b/test/packaging/test_wheel_contents.py new file mode 100644 index 000000000..f23dfeff9 --- /dev/null +++ b/test/packaging/test_wheel_contents.py @@ -0,0 +1,24 @@ +import shutil +import subprocess +import zipfile +from pathlib import Path + +import pytest + +REPO_ROOT = Path(__file__).parents[2] + + +@pytest.mark.skipif(shutil.which("uv") is None, reason="uv is required to build the wheel") +def test_wheel_ships_all_doc_guides(tmp_path): + subprocess.run( + ["uv", "build", "--wheel", "--out-dir", str(tmp_path)], + cwd=REPO_ROOT, + check=True, + capture_output=True, + ) + + wheel = next(tmp_path.glob("ecc-*.whl")) + names = zipfile.ZipFile(wheel).namelist() + for stem in ("config", "ug", "tutorial", "dev"): + for lang in ("en", "cn"): + assert f"chipcompiler/docs/ecc-cli-{stem}.{lang}.md" in names diff --git a/test/tools/ecc/test_runner.py b/test/tools/ecc/test_runner.py index 208346824..ee9b2378a 100644 --- a/test/tools/ecc/test_runner.py +++ b/test/tools/ecc/test_runner.py @@ -113,7 +113,7 @@ def test_run_analysis_switch(parameters, expected_calls, tmp_path, monkeypatch): plotter = Mock() checklist = Mock() monkeypatch.setattr(ecc_runner, "build_step_metrics", metrics) - monkeypatch.setattr(ecc_runner, "ECCToolsPlot", plotter) + monkeypatch.setattr("chipcompiler.tools.ecc.plot.ECCToolsPlot", plotter) monkeypatch.setattr(ecc_runner, "EccChecklist", checklist) ecc_runner.run_analysis(workspace=workspace, step=step, subflow=FakeSubFlow()) diff --git a/test/utility/test_plot_lazy.py b/test/utility/test_plot_lazy.py new file mode 100644 index 000000000..71ed5fe31 --- /dev/null +++ b/test/utility/test_plot_lazy.py @@ -0,0 +1,93 @@ +"""Regression tests: matplotlib must stay off the CLI/package import path. + +chipcompiler.utility re-exports the plot helpers lazily (PEP 562) so that +importing the package — and therefore every ecc command — does not pay for +matplotlib's import or its cold-cache fc-list font scan. +""" + +import os +import subprocess +import sys +from pathlib import Path + + +def _run_fresh(code: str, tmp_path: Path) -> subprocess.CompletedProcess: + env = os.environ | {"MPLCONFIGDIR": str(tmp_path / "mplconfig")} + return subprocess.run( + [sys.executable, "-c", code], + capture_output=True, + text=True, + check=True, + env=env, + ) + + +def test_utility_import_does_not_load_matplotlib(tmp_path): + result = _run_fresh( + "import sys, chipcompiler.utility; print('matplotlib' in sys.modules)", tmp_path + ) + assert result.stdout.strip() == "False" + + +def test_cli_help_does_not_load_matplotlib(tmp_path): + result = _run_fresh( + "import sys; from chipcompiler.cli.main import run; " + "rc = run(['--help']); " + "print(rc, 'matplotlib' in sys.modules)", + tmp_path, + ) + assert result.stdout.strip().splitlines()[-1] == "0 False" + + +def test_ecc_tools_probe_import_does_not_load_matplotlib(tmp_path): + # ecc doctor imports chipcompiler.tools.ecc.utility on a non-plot path. + result = _run_fresh( + "import sys; from chipcompiler.tools.ecc.utility import is_eda_exist; " + "print('matplotlib' in sys.modules)", + tmp_path, + ) + assert result.stdout.strip() == "False" + + +def test_plot_exports_resolve_lazily(tmp_path): + result = _run_fresh( + "import sys, chipcompiler.utility as u; " + "names = ['plot_bar_chart', 'plot_csv_bar_chart', 'plot_csv_map', " + "'plot_csv_table', 'plot_metrics']; " + "funcs = [getattr(u, n) for n in names]; " + "print(all(callable(f) for f in funcs), 'matplotlib' in sys.modules)", + tmp_path, + ) + assert result.stdout.strip() == "True True" + + +def test_ecc_tools_plot_export_resolves_lazily(tmp_path): + result = _run_fresh( + "import sys; from chipcompiler.tools.ecc import ECCToolsPlot; " + "print(callable(ECCToolsPlot), 'matplotlib' in sys.modules)", + tmp_path, + ) + assert result.stdout.strip() == "True True" + + +def test_unknown_attribute_still_raises(tmp_path): + result = _run_fresh( + "import chipcompiler.utility as u\n" + "try:\n" + " u.no_such_name\n" + "except AttributeError:\n" + " print('AttributeError')\n", + tmp_path, + ) + assert result.stdout.strip() == "AttributeError" + + +def test_plot_exports_stay_discoverable(tmp_path): + result = _run_fresh( + "import chipcompiler.utility as u; " + "print(all(n in dir(u) and n in u.__all__ for n in " + "['plot_bar_chart', 'plot_csv_bar_chart', 'plot_csv_map', " + "'plot_csv_table', 'plot_metrics']))", + tmp_path, + ) + assert result.stdout.strip() == "True"