diff --git a/chipcompiler/cli/core/output.py b/chipcompiler/cli/core/output.py index 6546e52ee..25e491ef5 100644 --- a/chipcompiler/cli/core/output.py +++ b/chipcompiler/cli/core/output.py @@ -13,7 +13,9 @@ def disclosure_cmd(command: str, project: str | None = None, workspace: str | No def normalize_step_name(internal: str) -> str: mapping = { "Synthesis": "synthesis", - "Floorplan": "floorplan", + "preFloorplan": "pre_floorplan", + "macroPlacement": "macro_placement", + "postFloorplan": "post_floorplan", "place": "placement", "CTS": "cts", "legalization": "legalization", diff --git a/chipcompiler/cli/project/design_inputs.py b/chipcompiler/cli/project/design_inputs.py index de321fdc1..b35b60fe0 100644 --- a/chipcompiler/cli/project/design_inputs.py +++ b/chipcompiler/cli/project/design_inputs.py @@ -23,6 +23,8 @@ class ResolvedDesignInputs: _PHYSICAL_STEPS = frozenset( { + "macroPlacement", + "postFloorplan", "place", "CTS", "legalization", @@ -46,7 +48,7 @@ def required_inputs_for_step(step: str) -> tuple[str, ...]: return ("rtl",) if canonical in {"lec", "postRouteLec"}: return ("netlist", "golden_netlist") - if canonical == "Floorplan": + if canonical == "preFloorplan": return ("netlist",) if canonical == "sta": return ("def", "netlist", "spef") diff --git a/chipcompiler/cli/project/manifest.py b/chipcompiler/cli/project/manifest.py index aaf329c85..a61239ec2 100644 --- a/chipcompiler/cli/project/manifest.py +++ b/chipcompiler/cli/project/manifest.py @@ -26,7 +26,9 @@ MANIFEST_FLOW_STEPS = ( "Synth", "LEC", - "Floor", + "PreFloorplan", + "MacroPlacement", + "PostFloorplan", "Place", "CTS", "Legal", @@ -54,7 +56,9 @@ _CANONICAL_TO_MANIFEST_STEP = { "Synthesis": "Synth", "lec": "LEC", - "Floorplan": "Floor", + "preFloorplan": "PreFloorplan", + "macroPlacement": "MacroPlacement", + "postFloorplan": "PostFloorplan", "place": "Place", "CTS": "CTS", "legalization": "Legal", diff --git a/chipcompiler/cli/project/migrate_plan.py b/chipcompiler/cli/project/migrate_plan.py index d097420c6..93463ef21 100644 --- a/chipcompiler/cli/project/migrate_plan.py +++ b/chipcompiler/cli/project/migrate_plan.py @@ -31,7 +31,9 @@ # Canonical step name -> GUI display name (manifest vocabulary). CANONICAL_TO_DISPLAY = { "Synthesis": "Synth", - "Floorplan": "Floor", + "preFloorplan": "PreFloorplan", + "macroPlacement": "MacroPlacement", + "postFloorplan": "PostFloorplan", "fixFanout": "Fanout", "place": "Place", "CTS": "CTS", diff --git a/chipcompiler/cli/project/workspace_params.py b/chipcompiler/cli/project/workspace_params.py index 07203d06f..e03481b02 100644 --- a/chipcompiler/cli/project/workspace_params.py +++ b/chipcompiler/cli/project/workspace_params.py @@ -15,7 +15,7 @@ _APPLIES_TO_STEP = { "synthesis": "Synthesis", - "floorplan": "Floorplan", + "floorplan": "preFloorplan", "placement": "place", "cts": "CTS", "routing": "route", diff --git a/chipcompiler/data/step.py b/chipcompiler/data/step.py index e4bcbd4b5..c12512871 100644 --- a/chipcompiler/data/step.py +++ b/chipcompiler/data/step.py @@ -11,7 +11,10 @@ class StepEnum(Enum): RTL2GDS = "RTL2GDS" INIT = "Init" SYNTHESIS = "Synthesis" - FLOORPLAN = "Floorplan" + FLOORPLAN = "Floorplan" # shared floorplan configuration key, not a flow step + PRE_FLOORPLAN = "preFloorplan" + MACRO_PLACEMENT = "macroPlacement" + POST_FLOORPLAN = "postFloorplan" PLACEMENT = "place" CTS = "CTS" TIMING_OPT = "Timing optimization" diff --git a/chipcompiler/data/step_dirs.py b/chipcompiler/data/step_dirs.py index 19125c287..2a91d1c88 100644 --- a/chipcompiler/data/step_dirs.py +++ b/chipcompiler/data/step_dirs.py @@ -14,7 +14,9 @@ STEP_DIRECTORIES = { StepEnum.SYNTHESIS.value: "Synthesis_yosys", StepEnum.LEC.value: "lec_yosys_lec", - StepEnum.FLOORPLAN.value: "Floorplan_ecc", + StepEnum.PRE_FLOORPLAN.value: "preFloorplan_ecc", + StepEnum.MACRO_PLACEMENT.value: "macroPlacement_dreamplace", + StepEnum.POST_FLOORPLAN.value: "postFloorplan_ecc", StepEnum.PLACEMENT.value: "place_dreamplace", StepEnum.CTS.value: "CTS_ecc", StepEnum.LEGALIZATION.value: "legalization_dreamplace", diff --git a/chipcompiler/data/workspace/__init__.py b/chipcompiler/data/workspace/__init__.py index 3ccdc2e94..247adc4ad 100644 --- a/chipcompiler/data/workspace/__init__.py +++ b/chipcompiler/data/workspace/__init__.py @@ -161,6 +161,7 @@ def log_workspace_step(step: WorkspaceStep, logger: Logger): StepEnum.CTS.value: "cts_ecc.json", StepEnum.DRC.value: "drc_ecc.json", StepEnum.FLOORPLAN.value: "floorplan_ecc.json", + "macro_location": "macro_localtion.tcl", StepEnum.ROUTING.value: "route_ecc.json", StepEnum.FILLER.value: "filler_ecc.json", StepEnum.RCX.value: "rcx_ecc.json", @@ -183,7 +184,9 @@ def log_workspace_step(step: WorkspaceStep, logger: Logger): _STEP_BY_VALUE: Final[dict[str, StepEnum]] = {step.value: step for step in StepEnum} _STEP_CONFIG_KEYS: Final[dict[tuple[StepEnum, str], tuple[str, ...]]] = { - (StepEnum.FLOORPLAN, "ecc"): ("db", StepEnum.FLOORPLAN.value), + (StepEnum.PRE_FLOORPLAN, "ecc"): ("db", StepEnum.FLOORPLAN.value), + (StepEnum.MACRO_PLACEMENT, "dreamplace"): ("dreamplace", "macro_location"), + (StepEnum.POST_FLOORPLAN, "ecc"): ("db", StepEnum.FLOORPLAN.value, "macro_location"), (StepEnum.PLACEMENT, "ecc"): ("db",), (StepEnum.CTS, "ecc"): ("db", StepEnum.CTS.value), (StepEnum.ROUTING, "ecc"): ("db", StepEnum.ROUTING.value), @@ -550,7 +553,7 @@ def _refresh_floorplan_config(workspace: Workspace, step: WorkspaceStep | None = default_ifp = default_floorplan.get("ifp", {}) ifp.setdefault("thread_number", default_ifp.get("thread_number", 16)) if step is not None: - workdir = step.data.workdir_for(StepEnum.FLOORPLAN.value) + workdir = step.data.workdir_for(step.name) if workdir: ifp["temp_directory_path"] = path_text(workdir) @@ -943,7 +946,7 @@ def update_step_config(workspace: Workspace, step: WorkspaceStep) -> None: db["OUTPUT"]["output_dir_path"] = path_text(step.output.dir) json_write(workspace.config["db"], db) - if step.name == StepEnum.FLOORPLAN.value: + if step.name in {StepEnum.PRE_FLOORPLAN.value, StepEnum.POST_FLOORPLAN.value}: _refresh_floorplan_config(workspace, step=step) if step.name == StepEnum.ROUTING.value and isinstance(step.data, EccData): diff --git a/chipcompiler/docs/ecc-config-ref.cn.md b/chipcompiler/docs/ecc-config-ref.cn.md index d3394fee3..0ecfb01bd 100644 --- a/chipcompiler/docs/ecc-config-ref.cn.md +++ b/chipcompiler/docs/ecc-config-ref.cn.md @@ -66,7 +66,9 @@ graph LR |---|---|---|---| | synthesis | — | `global_var.tcl`(Tcl) | Yosys 用 Tcl 变量驱动,不走 JSON | | lec | — | 无(Tcl) | 综合级 Yosys LEC;比较综合网表与 golden 网表;未证明时步骤失败并终止后续流程 | -| floorplan | ✓ | `floorplan_ecc.json` | | +| preFloorplan | ✓ | `floorplan_ecc.json` | 自动宏布局 | +| macroPlacement | — | `dreamplace_ecc.json` + `macro_localtion.tcl` | 写入 Tcl 宏摆放交接文件 | +| postFloorplan | ✓ | `floorplan_ecc.json` + `macro_localtion.tcl` | 读取 Tcl 宏摆放交接文件 | | placement | — | `dreamplace_ecc.json` | 与 legalization 共用一个文件 | | cts | ✓ | `cts_ecc.json` | | | legalization | — | `dreamplace_ecc.json` | 每步重写 `def_input`/`result_dir` 等 | @@ -228,13 +230,13 @@ tech = "prtech/techLEF/N551P6M_ecos.lef" ## 4. floorplan(ecc-tools) -配置 `floorplan_ecc.json`,按功能分 6 组。步骤内部子阶段:load data → init floorplan → create tracks → place io pins → tap cell → PDN → set clock net → save data → analysis。 +配置 `floorplan_ecc.json` 由 `preFloorplan` 和 `postFloorplan` 共享。`preFloorplan` 执行 load data → init simple floorplan → save data,并使用自动宏摆放;`macroPlacement` 执行仅宏单元摆放,并通过 `tcl_save` 写入 `config/macro_localtion.tcl` 形成交接检查点;`postFloorplan` 以 `file` 模式读取该文件,再执行 load data → create tracks → place IO pins → tap cells → PDN → set clock net → save data → analysis。 ### ifp(iFP 布图引擎) | 参数 | 默认 | 含义 | |---|---|---| -| `temp_directory_path` | 每步生成 → `Floorplan_ecc/data/fp` | iFP 中间数据目录 | +| `temp_directory_path` | 每步生成 → `preFloorplan_ecc/data/fp` 或 `postFloorplan_ecc/data/fp` | iFP 中间数据目录 | | `thread_number` | 16 | 并行线程数 | ### macro_placer(宏摆放) @@ -285,9 +287,9 @@ tech = "prtech/techLEF/N551P6M_ecos.lef" | `stripe`(MET4/MET5) | 宽 1.0、间距 16.0、偏移 0.5 | 电源条带:层/宽度/间距(pitch)/偏移(µm) | | `connect_layers` | MET1–MET4、MET4–MET5 | 相邻层电源过孔连接对 | -## 5. placement / legalization(DreamPlace) +## 5. macro placement / placement / legalization(DreamPlace) -两者共用 `config/dreamplace_ecc.json`;每次步骤运行前重写 `def_input`(placement 读 floorplan 输出,legalization 读 CTS 输出)、`verilog_input`、`result_dir`(分别为 `place_dreamplace/data/pl`、`legalization_dreamplace/data/pl`)。参数即上游 DreamPlace 的 JSON 参数集,分组解释如下(默认值 = 模板值;`*` = 用户参数映射点)。 +三步共用 `config/dreamplace_ecc.json`;每次步骤运行前重写 `def_input`、`verilog_input`、`result_dir`。`macroPlacement` 使用 `macroPlacement_dreamplace/data/macro`;placement 读取 post-floorplan 输出并使用 `place_dreamplace/data/pl`;legalization 读取 CTS 输出并使用 `legalization_dreamplace/data/pl`。参数即上游 DreamPlace 的 JSON 参数集,分组解释如下(默认值 = 模板值;`*` = 用户参数映射点)。 ### 输入输出 @@ -311,7 +313,7 @@ tech = "prtech/techLEF/N551P6M_ecos.lef" | `enable_fillers` | 1 | 布局阶段允许虚拟 filler 占位(密度计算) | | `routability_opt_flag` | 1 `*place.routability_opt` | 绕线拥塞驱动的布局优化 | | `timing_opt_flag` / `timing_eval_flag` | 0 | 时序驱动布局(本流程未启用,需 sizer/STA 配合) | -| `macro_place_flag` | 0 | 宏单元自动摆放(floorplan 已处理) | +| `macro_place_flag` | 0 | 专用 `macroPlacement` 步骤会启用该开关 | | `plot_flag` / `get_congestion_map` / `evaluate_pl` | 0 / 1 / 0 | 出图 / 拥塞图导出 / 布局评估 | | `dump_global_place_solution_flag` / `dump_legalize_solution_flag` | 0 | 导出中间解 | diff --git a/chipcompiler/docs/ecc-config-ref.en.md b/chipcompiler/docs/ecc-config-ref.en.md index 3cffec97c..6de0eb2a2 100644 --- a/chipcompiler/docs/ecc-config-ref.en.md +++ b/chipcompiler/docs/ecc-config-ref.en.md @@ -66,7 +66,9 @@ Distilled from real `ecc config ` output (maps to the source `_STEP_CONFIG |---|---|---|---| | synthesis | — | `global_var.tcl` (Tcl) | Yosys is driven by Tcl variables, not JSON | | lec | — | none (Tcl) | Synthesis-level Yosys LEC; compares the mapped and golden synthesis netlists; an unproven result fails the step and stops the flow | -| floorplan | ✓ | `floorplan_ecc.json` | | +| preFloorplan | ✓ | `floorplan_ecc.json` | automatic macro floorplanning | +| macroPlacement | — | `dreamplace_ecc.json` + `macro_localtion.tcl` | writes the Tcl macro-placement handoff | +| postFloorplan | ✓ | `floorplan_ecc.json` + `macro_localtion.tcl` | reads the Tcl macro-placement handoff | | placement | — | `dreamplace_ecc.json` | shares one file with legalization | | cts | ✓ | `cts_ecc.json` | | | legalization | — | `dreamplace_ecc.json` | `def_input`/`result_dir` etc. rewritten per step | @@ -226,13 +228,13 @@ There is also the environment variable `YOSYS_SYNTH_STRATEGY` (e.g. `DELAY 4` / ## 4. floorplan (ecc-tools) -Configuration file `floorplan_ecc.json`, organized into 6 functional groups. Internal sub-phases of the step: load data → init floorplan → create tracks → place io pins → tap cell → PDN → set clock net → save data → analysis. +Configuration file `floorplan_ecc.json` is shared by the `preFloorplan` and `postFloorplan` steps. `preFloorplan` runs load data → init simple floorplan → save data with automatic macro placement; `macroPlacement` runs macro-only placement, writes `config/macro_localtion.tcl` through `tcl_save`, and forms the handoff checkpoint; `postFloorplan` consumes that file in `file` mode, then runs load data → create tracks → place IO pins → tap cells → PDN → set clock net → save data → analysis. ### ifp (the iFP floorplan engine) | Parameter | Default | Meaning | |---|---|---| -| `temp_directory_path` | generated per step → `Floorplan_ecc/data/fp` | iFP intermediate data directory | +| `temp_directory_path` | generated per step → `preFloorplan_ecc/data/fp` or `postFloorplan_ecc/data/fp` | iFP intermediate data directory | | `thread_number` | 16 | Number of parallel threads | ### macro_placer (macro placement) @@ -283,9 +285,9 @@ Configuration file `floorplan_ecc.json`, organized into 6 functional groups. Int | `stripe` (MET4/MET5) | width 1.0, pitch 16.0, offset 0.5 | Power stripes: layer/width/pitch (spacing)/offset (µm) | | `connect_layers` | MET1–MET4, MET4–MET5 | Adjacent-layer via connection pairs for power | -## 5. placement / legalization (DreamPlace) +## 5. macro placement / placement / legalization (DreamPlace) -The two steps share `config/dreamplace_ecc.json`; before each step runs, `def_input` (placement reads the floorplan output, legalization reads the CTS output), `verilog_input`, and `result_dir` are rewritten (`place_dreamplace/data/pl` and `legalization_dreamplace/data/pl` respectively). The parameters are exactly the upstream DreamPlace JSON parameter set, explained group by group below (defaults = template values; `*` marks user-parameter mapping points). +The three steps share `config/dreamplace_ecc.json`; before each step runs, `def_input`, `verilog_input`, and `result_dir` are rewritten. `macroPlacement` uses `macroPlacement_dreamplace/data/macro`, placement reads the post-floorplan output and uses `place_dreamplace/data/pl`, and legalization reads the CTS output and uses `legalization_dreamplace/data/pl`. The parameters are exactly the upstream DreamPlace JSON parameter set, explained group by group below (defaults = template values; `*` marks user-parameter mapping points). ### Inputs and outputs @@ -309,7 +311,7 @@ The two steps share `config/dreamplace_ecc.json`; before each step runs, `def_in | `enable_fillers` | 1 | Allow virtual filler occupancy during placement (for density computation) | | `routability_opt_flag` | 1 `*place.routability_opt` | Routing-congestion-driven placement optimization | | `timing_opt_flag` / `timing_eval_flag` | 0 | Timing-driven placement (not enabled in this flow; requires sizer/STA support) | -| `macro_place_flag` | 0 | Automatic macro placement (already handled by floorplan) | +| `macro_place_flag` | 0 | Enabled for the dedicated `macroPlacement` step | | `plot_flag` / `get_congestion_map` / `evaluate_pl` | 0 / 1 / 0 | Plotting / congestion-map export / placement evaluation | | `dump_global_place_solution_flag` / `dump_legalize_solution_flag` | 0 | Export intermediate solutions | diff --git a/chipcompiler/docs/ecc-tutorial.cn.md b/chipcompiler/docs/ecc-tutorial.cn.md index dc843b356..384d4e1df 100644 --- a/chipcompiler/docs/ecc-tutorial.cn.md +++ b/chipcompiler/docs/ecc-tutorial.cn.md @@ -16,7 +16,7 @@ graph LR A[安装 ecc CLI
+ PDK + Yosys] --> B[ecc init gcd
建项目放 RTL] B --> C[ecc doctor / check
环境与配置体检] - C --> D[ecc run --preset rtl2gds
15 步全流程] + C --> D[ecc run --preset rtl2gds
17 步全流程] D --> E[ecc status / log
查看结果与日志] E --> F[ecc signoff export
签核包 tar.gz] E --> G[ecc report summary
设计总结报告] @@ -253,7 +253,7 @@ rc=0 ### 4.1 启动 -`rtl2gds` preset 是完整 15 步链,一步到位跑到 Harden(产出 GDS + 抽象 LEF + 时序 LIB): +`rtl2gds` preset 是完整 17 步链,一步到位跑到 Harden(产出 GDS + 抽象 LEF + 时序 LIB): ```bash ecc run --preset rtl2gds @@ -261,29 +261,31 @@ ecc run --preset rtl2gds (生成的 `ecc.toml` 已选择 `rtl2gds`;`--preset` 只对本次运行生效,不写回配置。) -交互终端下会实时渲染各步骤进度与日志尾部;输出重定向到文件时则静默执行,结束时打印汇总。`rtl2gds` 的 15 步依次为: +交互终端下会实时渲染各步骤进度与日志尾部;输出重定向到文件时则静默执行,结束时打印汇总。`rtl2gds` 的 17 步依次为: | # | 步骤 | 工具 | 作用 | |---|------|------|------| | 1 | synthesis | yosys | RTL 综合、工艺映射(slang 前端读入 SystemVerilog) | | 2 | lec | yosys_lec | 逻辑等价性检查:综合网表与 golden 网表 | -| 3 | floorplan | ecc | 布局规划:die/core 区域、IO pin 排布 | -| 4 | placement | dreamplace | 全局布局 | -| 5 | cts | ecc | 时钟树综合(含扇出约束) | -| 6 | legalization | dreamplace | 布局合法化 | -| 7 | timing optimization | sizer | 时序优化(cell sizing) | -| 8 | routing | ecc | 布线 | -| 9 | filler | ecc | 填充单元插入 | -| 10 | rcx | ecc | 寄生参数提取(多 corner SPEF) | -| 11 | sta | ecc | 多 corner 静态时序分析 | -| 12 | lvs | ecc | 版图与原理图一致性检查 | -| 13 | postroutelec | yosys_lec | 逻辑等价性检查:综合网表 vs 布线后网表 | -| 14 | drc | ecc | 物理规则检查 | -| 15 | harden | ecc | 硬化交付:GDS + 抽象 LEF + 时序 LIB + 版图快照 | +| 3 | pre_floorplan | ecc | 执行 simple floorplan,并使用自动 macro placement | +| 4 | macro_placement | dreamplace | 执行仅宏单元摆放;这是宏位置文件被读取前的交接检查点 | +| 5 | post_floorplan | ecc | 读取宏位置文件,完成 tracks、IO pin、tap cell、PDN 和时钟网设置 | +| 6 | placement | dreamplace | 全局布局 | +| 7 | cts | ecc | 时钟树综合(含扇出约束) | +| 8 | legalization | dreamplace | 布局合法化 | +| 9 | timing optimization | sizer | 时序优化(cell sizing) | +| 10 | routing | ecc | 布线 | +| 11 | filler | ecc | 填充单元插入 | +| 12 | rcx | ecc | 寄生参数提取(多 corner SPEF) | +| 13 | sta | ecc | 多 corner 静态时序分析 | +| 14 | lvs | ecc | 版图与原理图一致性检查 | +| 15 | postroutelec | yosys_lec | 逻辑等价性检查:综合网表 vs 布线后网表 | +| 16 | drc | ecc | 物理规则检查 | +| 17 | harden | ecc | 硬化交付:GDS + 抽象 LEF + 时序 LIB + 版图快照 | ```mermaid graph LR - A[Synthesis
yosys] --> Q[LEC
yosys_lec] --> B[Floorplan] --> D[Placement
dreamplace] + A[Synthesis
yosys] --> Q[LEC
yosys_lec] --> B[Pre Floorplan] --> C[Macro Placement
dreamplace] --> P[Post Floorplan] --> D[Placement
dreamplace] D --> E[CTS] --> F[Legalization
dreamplace] --> T[Timing Opt
sizer] --> G[Routing] G --> J[Filler] --> K[RCX] --> L[STA] --> I[LVS] --> N[LEC
yosys_lec] --> H[DRC] --> M[Harden
GDS/LEF/LIB] ``` @@ -312,8 +314,12 @@ $ ecc status log: ecc log synthesis --workspace default lec (yosys_lec) success 0:0:1 log: ecc log lec --workspace default - floorplan (ecc) success 0:0:1 - log: ecc log floorplan --workspace default + pre_floorplan (ecc) success 0:0:1 + log: ecc log pre_floorplan --workspace default + macro_placement (dreamplace) success 0:0:5 + log: ecc log macro_placement --workspace default + post_floorplan (ecc) success 0:0:1 + log: ecc log post_floorplan --workspace default placement (dreamplace) ongoing 0:0:40 log: ecc log placement --workspace default cts (ecc) unstart @@ -338,7 +344,9 @@ $ ecc status synthesis (yosys) success 0:0:17 log: ecc log synthesis --workspace default lec (yosys_lec) success 0:0:1 - floorplan (ecc) success 0:0:1 + pre_floorplan (ecc) success 0:0:1 + macro_placement (dreamplace) success 0:0:5 + post_floorplan (ecc) success 0:0:1 placement (dreamplace) success 0:0:47 cts (ecc) success 0:0:19 legalization (dreamplace) success 0:0:1 @@ -379,7 +387,9 @@ default/ ├── config/ # 各步骤实际生效的配置(ecc config 查看) ├── Synthesis_yosys/ # 每步子目录内含 log/ script/ output/ report/ 等分类 ├── lec_yosys_lec/ # 综合级 LEC 等价性检查 -├── Floorplan_ecc/ +├── preFloorplan_ecc/ +├── macroPlacement_dreamplace/ +├── postFloorplan_ecc/ ├── ... ├── postRouteLec_yosys_lec/ # LEC 等价性检查(output/_postRouteLec_result.json) ├── Harden_ecc/ @@ -649,24 +659,24 @@ ecc run --workspace default --only place --force # 已成功也强制重跑这 ```bash ecc run --workspace cts-only --from cts --to cts # 只跑 CTS 一步的 workspace -ecc run --workspace pnr --from floorplan --to route # 从布局规划到布线 +ecc run --workspace pnr --from prefloorplan --to route # 从 pre-floorplan 到布线 ``` -**复用已有 Floorplan 输出的完整示例**:从已有 workspace `2` 的 Floorplan 输出创建一个新的 placement 到 routing workspace 时,不能把 DEF 直接传给 `ecc run`。placement 入口要求匹配的 `design.def` 和 `design.netlist`;先用 `ecc project` 写入项目配置,再创建新范围: +**复用已有 post-floorplan 输出的完整示例**:从已有 workspace `2` 的 post-floorplan 输出创建一个新的 placement 到 routing workspace 时,不能把 DEF 直接传给 `ecc run`。placement 入口要求匹配的 `design.def` 和 `design.netlist`;先用 `ecc project` 写入项目配置,再创建新范围: ```bash PROJECT=~/projects/benchmark/gcd -SOURCE="$PROJECT/2/Floorplan_ecc/output" +SOURCE="$PROJECT/2/postFloorplan_ecc/output" -ecc project set design.def "$SOURCE/gcd_Floorplan.def.gz" --project "$PROJECT" -ecc project set design.netlist "$SOURCE/gcd_Floorplan.v.gz" --project "$PROJECT" +ecc project set design.def "$SOURCE/gcd_postFloorplan.def.gz" --project "$PROJECT" +ecc project set design.netlist "$SOURCE/gcd_postFloorplan.v.gz" --project "$PROJECT" ecc run --project "$PROJECT" --workspace floorplan-2-place-route \ --from placement --to routing ``` -这会将 DEF/网表复制到新 workspace 的 `origin/`,从 placement 运行至 routing,不会重新运行 Floorplan;`placement`、`routing` 是新建范围时可用的别名。`ecc project set` 修改的是项目级 `ecc.toml`,也会影响之后新建的 workspace。若这两个字段原来没有设置,在新 workspace 创建后执行 `ecc project unset design.def --project "$PROJECT"` 和 `ecc project unset design.netlist --project "$PROJECT"` 可恢复原先的项目入口。 +这会将 DEF/网表复制到新 workspace 的 `origin/`,从 placement 运行至 routing,不会重新运行 post-floorplan;`placement`、`routing` 是新建范围时可用的别名。`ecc project set` 修改的是项目级 `ecc.toml`,也会影响之后新建的 workspace。若这两个字段原来没有设置,在新 workspace 创建后执行 `ecc project unset design.def --project "$PROJECT"` 和 `ecc project unset design.netlist --project "$PROJECT"` 可恢复原先的项目入口。 -入口文件按首步要求校验:Synthesis 要 `rtl`;LEC 要 `netlist` 和 `golden_netlist`;Floorplan 要 `netlist`;物理步骤(place/CTS/legalization/timing optimization/route/filler/rcx/drc/lvs/harden)要 `def` 和 `netlist`;STA 要 `def`、`netlist`、`spef`;`sdc` 可选。缺什么会明确报错: +入口文件按首步要求校验:Synthesis 要 `rtl`;LEC 要 `netlist` 和 `golden_netlist`;pre-floorplan 要 `netlist`;macro placement、post-floorplan 和其余物理步骤(place/CTS/legalization/timing optimization/route/filler/rcx/drc/lvs/harden)要 `def` 和 `netlist`;STA 要 `def`、`netlist`、`spef`;`sdc` 可选。缺什么会明确报错: ```console $ ecc run --from cts --to route @@ -681,8 +691,8 @@ rc=1 > ```console > $ ecc run --workspace default --only placemen # 拼错了:既不是持久化名也不是别名 > [error] -> unknown_step unknown step 'placemen'; available steps: Synthesis, lec, Floorplan, -> place, CTS, legalization, Timing optimization, route, filler, RCX, sta, lvs, +> unknown_step unknown step 'placemen'; available steps: Synthesis, lec, preFloorplan, +> macroPlacement, postFloorplan, place, CTS, legalization, Timing optimization, route, filler, RCX, sta, lvs, > postRouteLec, drc, Harden > ``` @@ -715,7 +725,7 @@ ecc config --plain # 项目级配置(键值 + 解析后绝对路径) ## 8. 下一步 - 换你自己的设计:改 `ecc.toml` 的 `top`/`rtl`/`clock_port`/`frequency_mhz`,多文件用 [filelist](https://github.com/openecos-projects/ecc/blob/main/docs/examples/gcd/README.md#using-filelist); -- 了解 preset 差异:`rtl2gds`(完整 15 步综合到 Harden 链,含综合级 LEC)、`syn_sta`(仅综合)、`synthesis_lec`(综合 + LEC,两步); +- 了解 preset 差异:`rtl2gds`(完整 17 步综合到 Harden 链,含综合级 LEC)、`syn_sta`(仅综合)、`synthesis_lec`(综合 + LEC,两步); - 全部命令细节见 **[ECC CLI 用户指南](ecc-user-guide.cn.md)**(终端:`ecc doc ug --lang cn`);CLI 扩展开发见 [development.cn.md](https://github.com/openecos-projects/ecc/blob/main/docs/development.cn.md#扩展-cli); - 用 Python API 直接编排 flow(`EngineFlow`)见 [examples/gcd/ics55flow.py](https://github.com/openecos-projects/ecc/blob/main/docs/examples/gcd/ics55flow.py)。 diff --git a/chipcompiler/docs/ecc-tutorial.en.md b/chipcompiler/docs/ecc-tutorial.en.md index 89f2d70e3..5e2ff8a03 100644 --- a/chipcompiler/docs/ecc-tutorial.en.md +++ b/chipcompiler/docs/ecc-tutorial.en.md @@ -16,7 +16,7 @@ The target process is the official [ICS55 PDK](https://github.com/openecos-proje graph LR A[Install ecc CLI
+ PDK + Yosys] --> B[ecc init gcd
create project, add RTL] B --> C[ecc doctor / check
environment & config checks] - C --> D[ecc run --preset rtl2gds
15-step flow] + C --> D[ecc run --preset rtl2gds
17-step flow] D --> E[ecc status / log
inspect results & logs] E --> F[ecc signoff export
signoff tar.gz] E --> G[ecc report summary
design summary] @@ -254,7 +254,7 @@ rc=0 ### 4.1 Start -The `rtl2gds` preset is the full 15-step chain, running all the way through Harden (which produces the GDS + abstract LEF + timing LIB): +The `rtl2gds` preset is the full 17-step chain, running all the way through Harden (which produces the GDS + abstract LEF + timing LIB): ```bash ecc run --preset rtl2gds @@ -262,29 +262,31 @@ ecc run --preset rtl2gds (The generated `ecc.toml` already selects `rtl2gds`; `--preset` applies to this run only and is not written back.) -In an interactive terminal the CLI renders live per-step progress and log tails; with output redirected to a file it runs silently and prints a summary at the end. The 15 `rtl2gds` steps are: +In an interactive terminal the CLI renders live per-step progress and log tails; with output redirected to a file it runs silently and prints a summary at the end. The 17 `rtl2gds` steps are: | # | Step | Tool | What it does | |---|------|------|--------------| | 1 | synthesis | yosys | RTL synthesis and technology mapping (slang frontend reads SystemVerilog) | | 2 | lec | yosys_lec | Logic equivalence check: synthesis netlist vs its golden netlist | -| 3 | floorplan | ecc | Floorplan: die/core regions, IO pin placement | -| 4 | placement | dreamplace | Global placement | -| 5 | cts | ecc | Clock tree synthesis (incl. fanout limits) | -| 6 | legalization | dreamplace | Placement legalization | -| 7 | timing optimization | sizer | Timing optimization (cell sizing) | -| 8 | routing | ecc | Routing | -| 9 | filler | ecc | Filler cell insertion | -| 10 | rcx | ecc | Parasitic extraction (multi-corner SPEF) | -| 11 | sta | ecc | Multi-corner static timing analysis | -| 12 | lvs | ecc | Layout-vs-schematic check | -| 13 | postroutelec | yosys_lec | Logic equivalence check: synthesis netlist vs post-route netlist | -| 14 | drc | ecc | Design rule check | -| 15 | harden | ecc | Hardened handoff: GDS + abstract LEF + timing LIB + layout snapshot | +| 3 | pre_floorplan | ecc | Build the simple floorplan with automatic macro placement | +| 4 | macro_placement | dreamplace | Run macro-only placement; this is the handoff checkpoint before the macro-location file is consumed | +| 5 | post_floorplan | ecc | Read the macro-location file; create tracks, IO pins, tap cells, PDN, and clock-net setup | +| 6 | placement | dreamplace | Global placement | +| 7 | cts | ecc | Clock tree synthesis (incl. fanout limits) | +| 8 | legalization | dreamplace | Placement legalization | +| 9 | timing optimization | sizer | Timing optimization (cell sizing) | +| 10 | routing | ecc | Routing | +| 11 | filler | ecc | Filler cell insertion | +| 12 | rcx | ecc | Parasitic extraction (multi-corner SPEF) | +| 13 | sta | ecc | Multi-corner static timing analysis | +| 14 | lvs | ecc | Layout-vs-schematic check | +| 15 | postroutelec | yosys_lec | Logic equivalence check: synthesis netlist vs post-route netlist | +| 16 | drc | ecc | Design rule check | +| 17 | harden | ecc | Hardened handoff: GDS + abstract LEF + timing LIB + layout snapshot | ```mermaid graph LR - A[Synthesis
yosys] --> Q[LEC
yosys_lec] --> B[Floorplan] --> D[Placement
dreamplace] + A[Synthesis
yosys] --> Q[LEC
yosys_lec] --> B[Pre Floorplan] --> C[Macro Placement
dreamplace] --> P[Post Floorplan] --> D[Placement
dreamplace] D --> E[CTS] --> F[Legalization
dreamplace] --> T[Timing Opt
sizer] --> G[Routing] G --> J[Filler] --> K[RCX] --> L[STA] --> I[LVS] --> N[LEC
yosys_lec] --> H[DRC] --> M[Harden
GDS/LEF/LIB] ``` @@ -313,8 +315,12 @@ $ ecc status log: ecc log synthesis --workspace default lec (yosys_lec) success 0:0:1 log: ecc log lec --workspace default - floorplan (ecc) success 0:0:1 - log: ecc log floorplan --workspace default + pre_floorplan (ecc) success 0:0:1 + log: ecc log pre_floorplan --workspace default + macro_placement (dreamplace) success 0:0:5 + log: ecc log macro_placement --workspace default + post_floorplan (ecc) success 0:0:1 + log: ecc log post_floorplan --workspace default placement (dreamplace) ongoing 0:0:40 log: ecc log placement --workspace default cts (ecc) unstart @@ -339,7 +345,9 @@ $ ecc status synthesis (yosys) success 0:0:17 log: ecc log synthesis --workspace default lec (yosys_lec) success 0:0:1 - floorplan (ecc) success 0:0:1 + pre_floorplan (ecc) success 0:0:1 + macro_placement (dreamplace) success 0:0:5 + post_floorplan (ecc) success 0:0:1 placement (dreamplace) success 0:0:47 cts (ecc) success 0:0:19 legalization (dreamplace) success 0:0:1 @@ -380,7 +388,9 @@ default/ ├── config/ # configs actually in effect per step (view: ecc config ) ├── Synthesis_yosys/ # each step dir is organized into log/ script/ output/ report/ ... ├── lec_yosys_lec/ # synthesis-level LEC equivalence check -├── Floorplan_ecc/ +├── preFloorplan_ecc/ +├── macroPlacement_dreamplace/ +├── postFloorplan_ecc/ ├── ... ├── postRouteLec_yosys_lec/ # LEC equivalence check (output/_postRouteLec_result.json) ├── Harden_ecc/ @@ -650,24 +660,24 @@ ecc run --workspace default --only place --force # rerun this step even if it ```bash ecc run --workspace cts-only --from cts --to cts # a workspace that runs only the CTS step -ecc run --workspace pnr --from floorplan --to route # floorplan through routing +ecc run --workspace pnr --from prefloorplan --to route # pre-floorplan through routing ``` -**Complete example: reuse existing Floorplan outputs.** To create a placement-through-routing workspace from the Floorplan output of existing workspace `2`, do not pass the DEF directly to `ecc run`. The placement entry requires matching `design.def` and `design.netlist`; write those project declarations first, then create the new range: +**Complete example: reuse existing post-floorplan outputs.** To create a placement-through-routing workspace from the post-floorplan output of existing workspace `2`, do not pass the DEF directly to `ecc run`. The placement entry requires matching `design.def` and `design.netlist`; write those project declarations first, then create the new range: ```bash PROJECT=~/projects/benchmark/gcd -SOURCE="$PROJECT/2/Floorplan_ecc/output" +SOURCE="$PROJECT/2/postFloorplan_ecc/output" -ecc project set design.def "$SOURCE/gcd_Floorplan.def.gz" --project "$PROJECT" -ecc project set design.netlist "$SOURCE/gcd_Floorplan.v.gz" --project "$PROJECT" +ecc project set design.def "$SOURCE/gcd_postFloorplan.def.gz" --project "$PROJECT" +ecc project set design.netlist "$SOURCE/gcd_postFloorplan.v.gz" --project "$PROJECT" ecc run --project "$PROJECT" --workspace floorplan-2-place-route \ --from placement --to routing ``` -This copies the DEF/netlist into the new workspace's `origin/`, runs placement through routing, and does not rerun Floorplan; `placement` and `routing` are accepted aliases when creating a range. `ecc project set` changes project-level `ecc.toml`, which also affects later fresh workspaces. If the two fields were previously unset, restore the original project entry after creation with `ecc project unset design.def --project "$PROJECT"` and `ecc project unset design.netlist --project "$PROJECT"`. +This copies the DEF/netlist into the new workspace's `origin/`, runs placement through routing, and does not rerun post-floorplan; `placement` and `routing` are accepted aliases when creating a range. `ecc project set` changes project-level `ecc.toml`, which also affects later fresh workspaces. If the two fields were previously unset, restore the original project entry after creation with `ecc project unset design.def --project "$PROJECT"` and `ecc project unset design.netlist --project "$PROJECT"`. -The declared entry files are validated against the first step's requirements: `rtl` for Synthesis; `netlist` plus `golden_netlist` for LEC; `netlist` for Floorplan; `def` plus `netlist` for the physical steps (place/CTS/legalization/timing optimization/route/filler/rcx/drc/lvs/harden); and `def`, `netlist`, and `spef` for STA. `sdc` is optional. Anything missing fails with a clear error: +The declared entry files are validated against the first step's requirements: `rtl` for Synthesis; `netlist` plus `golden_netlist` for LEC; `netlist` for pre-floorplan; `def` plus `netlist` for macro placement, post-floorplan, and the remaining physical steps (place/CTS/legalization/timing optimization/route/filler/rcx/drc/lvs/harden); and `def`, `netlist`, and `spef` for STA. `sdc` is optional. Anything missing fails with a clear error: ```console $ ecc run --from cts --to route @@ -682,8 +692,8 @@ rc=1 > ```console > $ ecc run --workspace default --only placemen # typo: neither a persisted name nor an alias > [error] -> unknown_step unknown step 'placemen'; available steps: Synthesis, lec, Floorplan, -> place, CTS, legalization, Timing optimization, route, filler, RCX, sta, lvs, +> unknown_step unknown step 'placemen'; available steps: Synthesis, lec, preFloorplan, +> macroPlacement, postFloorplan, place, CTS, legalization, Timing optimization, route, filler, RCX, sta, lvs, > postRouteLec, drc, Harden > ``` @@ -716,7 +726,7 @@ 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](https://github.com/openecos-projects/ecc/blob/main/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); +- Preset differences: `rtl2gds` (the complete 17-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-user-guide.en.md)** (`ecc doc ug`); extending the CLI is covered in [development.md](https://github.com/openecos-projects/ecc/blob/main/docs/development.md#extending-the-cli); - Driving the flow directly via the Python API (`EngineFlow`): [examples/gcd/ics55flow.py](https://github.com/openecos-projects/ecc/blob/main/docs/examples/gcd/ics55flow.py). diff --git a/chipcompiler/docs/ecc-user-guide.cn.md b/chipcompiler/docs/ecc-user-guide.cn.md index 4cc5c1a1a..20d9ccce7 100644 --- a/chipcompiler/docs/ecc-user-guide.cn.md +++ b/chipcompiler/docs/ecc-user-guide.cn.md @@ -84,8 +84,8 @@ uv run ecc --help - 结构化输出:`init`、`check`、`run`、`status`、`log`、`config`、`migrate`、`doctor`、`param`、`pdk`、`project`、`workspace`、`signoff`、`report` 都支持 `--plain`(`key=value`,便于脚本解析),缺省为人类可读 TEXT。`rpc serve` 和 `layout-image` 使用各自的协议。 - 退出码:成功 0;业务失败 1(错误记录形如 `[error] error=<机器可读错误码>`)。 - 步骤名(step token)有三套写法,按场景区分: - - **展示名**(`ecc status` / `ecc log` / `ecc report step` 的输出与入参,统一小写/下划线):`synthesis / lec / floorplan / placement / cts / legalization / timing_optimization / routing / filler / rcx / sta / lvs / postroutelec / drc / harden`; - - **持久化名**(`home/flow.json` 中的原始名;已有 workspace 上的 `--from`/`--only`/`--to` 必须用它,如 `place`、`CTS`、`Timing optimization`):`Synthesis / lec / Floorplan / place / CTS / legalization / Timing optimization / route / filler / RCX / sta / lvs / postRouteLec / drc / Harden`; + - **展示名**(`ecc status` / `ecc log` / `ecc report step` 的输出与入参,统一小写/下划线):`synthesis / lec / pre_floorplan / macro_placement / post_floorplan / placement / cts / legalization / timing_optimization / routing / filler / rcx / sta / lvs / postroutelec / drc / harden`; + - **持久化名**(`home/flow.json` 中的原始名;已有 workspace 上的 `--from`/`--only`/`--to` 必须用它,如 `place`、`CTS`、`Timing optimization`):`Synthesis / lec / preFloorplan / macroPlacement / postFloorplan / place / CTS / legalization / Timing optimization / route / filler / RCX / sta / lvs / postRouteLec / drc / Harden`; - **新建范围时的别名**(首次 `--from A --to B` 建 workspace 会做别名归一化,两种拼法都接受):如 `cts`↔`CTS`、`route`↔`routing`、`timingopt`↔`Timing optimization`、`postlec`↔`postRouteLec`。 拼错时返回 `unknown_step` 并列出全部可用步骤名,照抄即可。 @@ -327,7 +327,7 @@ ecc run [OPTIONS] --plain 面向脚本的 key=value 输出 ``` -新建或 `--overwrite` 的 workspace 会按以下流程执行:读 `ecc.toml` → 只解析入口步骤所需的设计文件以及 PDK/参数 → 预检所需工具 → 先写入 `project.json` 登记 → 在 `/` 创建 workspace → 将声明的设计输入复制到 `origin/`、写入对应步骤配置并运行 flow。workspace 不会存放第二份项目输入清单。已有 workspace 按持久化 flow 续跑,不会改写已有输入或步骤配置。`rtl2gds` 是完整 15 步链(Synthesis→LEC(Yosys 等价性检查)→Floorplan→place→CTS→legalization→Timing optimization(sizer)→route→filler→RCX→sta→LVS→postRouteLec(Yosys 等价性检查)→DRC→Harden,Harden 产出 GDS + 抽象 LEF + 时序 LIB)。 +新建或 `--overwrite` 的 workspace 会按以下流程执行:读 `ecc.toml` → 只解析入口步骤所需的设计文件以及 PDK/参数 → 预检所需工具 → 先写入 `project.json` 登记 → 在 `/` 创建 workspace → 将声明的设计输入复制到 `origin/`、写入对应步骤配置并运行 flow。workspace 不会存放第二份项目输入清单。已有 workspace 按持久化 flow 续跑,不会改写已有输入或步骤配置。`rtl2gds` 是完整 17 步链(Synthesis→LEC(Yosys 等价性检查)→preFloorplan→macroPlacement→postFloorplan→place→CTS→legalization→Timing optimization(sizer)→route→filler→RCX→sta→LVS→postRouteLec(Yosys 等价性检查)→DRC→Harden,Harden 产出 GDS + 抽象 LEF + 时序 LIB)。 运行结束打印汇总(真实输出): @@ -383,7 +383,7 @@ ecc run --workspace cts-only --from cts --to cts # 新建范围 workspace ecc run --workspace cts-route --from cts --to routing # 同上;两端都接受别名 ``` -新建范围 workspace 时只校验**入口步骤**所需的设计输入:Synthesis 要 `rtl`;LEC/postRouteLec 要 `netlist` + `golden_netlist`;Floorplan 要 `netlist`;物理步骤(place/CTS/legalization/timing optimization/route/filler/rcx/drc/lvs/harden)要 `def` + `netlist`;sta 还要 `spef`;`sdc` 声明了才校验。缺输入时按 `step_input_missing` 报错: +新建范围 workspace 时只校验**入口步骤**所需的设计输入:Synthesis 要 `rtl`;LEC/postRouteLec 要 `netlist` + `golden_netlist`;preFloorplan 要 `netlist`;macroPlacement、postFloorplan 和其余物理步骤(place/CTS/legalization/timing optimization/route/filler/rcx/drc/lvs/harden)要 `def` + `netlist`;sta 还要 `spef`;`sdc` 声明了才校验。缺输入时按 `step_input_missing` 报错: ```console $ ecc run --from cts --to route # 新建范围但缺 def/netlist @@ -393,19 +393,19 @@ $ ecc run --from cts --to route # 新建范围但缺 def/netlist rc=1 ``` -例如,要复用已有 workspace `2` 的 Floorplan 产物,新建一个只跑 placement 到 routing 的 workspace,先把**匹配的一对** DEF 和门级网表声明为新 workspace 的入口输入,再创建范围 flow: +例如,要复用已有 workspace `2` 的 post-floorplan 产物,新建一个只跑 placement 到 routing 的 workspace,先把**匹配的一对** DEF 和门级网表声明为新 workspace 的入口输入,再创建范围 flow: ```bash PROJECT=~/projects/benchmark/gcd -SOURCE="$PROJECT/2/Floorplan_ecc/output" +SOURCE="$PROJECT/2/postFloorplan_ecc/output" -ecc project set design.def "$SOURCE/gcd_Floorplan.def.gz" --project "$PROJECT" -ecc project set design.netlist "$SOURCE/gcd_Floorplan.v.gz" --project "$PROJECT" +ecc project set design.def "$SOURCE/gcd_postFloorplan.def.gz" --project "$PROJECT" +ecc project set design.netlist "$SOURCE/gcd_postFloorplan.v.gz" --project "$PROJECT" ecc run --project "$PROJECT" --workspace floorplan-2-place-route \ --from placement --to routing ``` -`ecc run` 不提供 `--def` 或 `--netlist` 选项;范围入口从 `ecc.toml` 的 `design.def` / `design.netlist` 读取。这个例子会在 `project.json` 中登记 `floorplan-2-place-route`,将两个文件复制到新 workspace 的 `origin/`,并从 placement 开始执行至 routing(不重跑 Floorplan)。由于前两条命令会改动项目级 `ecc.toml`,它们也影响之后新建的 workspace;若原先未声明这些字段,可在创建完成后用 `ecc project unset design.def --project "$PROJECT"` 和 `ecc project unset design.netlist --project "$PROJECT"` 恢复项目默认入口。 +`ecc run` 不提供 `--def` 或 `--netlist` 选项;范围入口从 `ecc.toml` 的 `design.def` / `design.netlist` 读取。这个例子会在 `project.json` 中登记 `floorplan-2-place-route`,将两个文件复制到新 workspace 的 `origin/`,并从 placement 开始执行至 routing(不重跑 postFloorplan)。由于前两条命令会改动项目级 `ecc.toml`,它们也影响之后新建的 workspace;若原先未声明这些字段,可在创建完成后用 `ecc project unset design.def --project "$PROJECT"` 和 `ecc project unset design.netlist --project "$PROJECT"` 恢复项目默认入口。 ### 5.2 workspace 模式(调试/复跑) @@ -423,8 +423,8 @@ ecc run [--workspace NAME] [--resume | --from STEP [--to STEP] | --only STEP [-- ```console $ ecc run --workspace default --from synthesis # 持久化名是 "Synthesis" [error] - unknown_step unknown step 'synthesis'; available steps: Synthesis, lec, Floorplan, - place, CTS, legalization, Timing optimization, route, filler, RCX, sta, lvs, + unknown_step unknown step 'synthesis'; available steps: Synthesis, lec, preFloorplan, + macroPlacement, postFloorplan, place, CTS, legalization, Timing optimization, route, filler, RCX, sta, lvs, postRouteLec, drc, Harden workspace: /tmp/gcd/default ``` @@ -523,8 +523,12 @@ $ ecc status log: ecc log synthesis --workspace default lec (yosys_lec) success 0:0:1 log: ecc log lec --workspace default - floorplan (ecc) success 0:0:1 - log: ecc log floorplan --workspace default + pre_floorplan (ecc) success 0:0:1 + log: ecc log pre_floorplan --workspace default + macro_placement (dreamplace) success 0:0:5 + log: ecc log macro_placement --workspace default + post_floorplan (ecc) success 0:0:1 + log: ecc log post_floorplan --workspace default placement (dreamplace) incomplete log: ecc log placement --workspace default cts (ecc) unstart @@ -922,18 +926,20 @@ ecc report checklist --project gcd - `checklist`:`/checklist.json`(v3 契约,缺失时回退 `home/checklist.json` 按步骤过滤) - `--section` 可重复指定,只输出选中的节;某节产物缺失时该节显示 `unavailable` -step token 与 `ecc log` 同源(`synthesis/floorplan/placement/cts/...`),同时接受 flow 内部名(如 `Timing optimization`)与目录名变体(`timing_optimization`);未知 token 返回 `unknown_step` 并列出可用值。 +step token 与 `ecc log` 同源(`synthesis/pre_floorplan/macro_placement/post_floorplan/placement/cts/...`),同时接受 flow 内部名(如 `Timing optimization`)与目录名变体(`timing_optimization`);未知 token 返回 `unknown_step` 并列出可用值。 ```console $ ecc report step --workspace default [report step] workspace : /tmp/gcd/default - steps : 15 + steps : 17 step tool status runtime peak MB metrics quality checklist synthesis yosys success 0:0:17 1165.89 10 pass ready lec yosys_lec success 0:0:1 0.164 - - ready - floorplan ecc success 0:0:1 97.516 11 pass ready + pre_floorplan ecc success 0:0:1 97.516 - - ready + macro_placement dreamplace success 0:0:5 97.516 - - ready + post_floorplan ecc success 0:0:1 97.516 11 pass ready ... drc ecc success 0:0:3 42.0 12 blocked blocked (1 blocked) @@ -1030,7 +1036,7 @@ ecc report qor --workspace baseline # 两个 run 的 QoR 报告分别对比 ecc report qor --workspace exp1 # 已有现成综合网表时,也可以从中间步骤起建范围 workspace(入口输入要求见 §5.1): -ecc run --workspace pnr --from floorplan --to route +ecc run --workspace pnr --from prefloorplan --to route ``` `project.json` 生成后,项目级查看、签核和报告命令按已声明的 workspace 选择;只有一个活跃 workspace 时自动选中,多个活跃 workspace 时必须显式传 `--workspace NAME`(否则报 `workspace_required` 并列出可用名称)。不再使用的 workspace 可在 `project.json` 中把其 `status` 改为 `archived`,使其退出自动选择。 diff --git a/chipcompiler/docs/ecc-user-guide.en.md b/chipcompiler/docs/ecc-user-guide.en.md index 16abb4c33..887723700 100644 --- a/chipcompiler/docs/ecc-user-guide.en.md +++ b/chipcompiler/docs/ecc-user-guide.en.md @@ -84,8 +84,8 @@ uv run ecc --help - Structured output: `init`, `check`, `run`, `status`, `log`, `config`, `migrate`, `doctor`, `param`, `pdk`, `project`, `workspace`, `signoff`, and `report` accept `--plain` (`key=value`, for scripting), with human-readable TEXT by default. `rpc serve` and `layout-image` use their own protocols instead. - Exit codes: 0 on success; 1 on business failure (error records look like `[error] error=`). - Step tokens come in three vocabularies, distinguished by context: - - **display names** (output and input of `ecc status` / `ecc log` / `ecc report step`, uniformly lowercase/underscore): `synthesis / lec / floorplan / placement / cts / legalization / timing_optimization / routing / filler / rcx / sta / lvs / postroutelec / drc / harden`; - - **persisted names** (the original names in `home/flow.json`; required by `--from`/`--only`/`--to` on existing workspaces, e.g. `place`, `CTS`, `Timing optimization`): `Synthesis / lec / Floorplan / place / CTS / legalization / Timing optimization / route / filler / RCX / sta / lvs / postRouteLec / drc / Harden`; + - **display names** (output and input of `ecc status` / `ecc log` / `ecc report step`, uniformly lowercase/underscore): `synthesis / lec / pre_floorplan / macro_placement / post_floorplan / placement / cts / legalization / timing_optimization / routing / filler / rcx / sta / lvs / postroutelec / drc / harden`; + - **persisted names** (the original names in `home/flow.json`; required by `--from`/`--only`/`--to` on existing workspaces, e.g. `place`, `CTS`, `Timing optimization`): `Synthesis / lec / preFloorplan / macroPlacement / postFloorplan / place / CTS / legalization / Timing optimization / route / filler / RCX / sta / lvs / postRouteLec / drc / Harden`; - **aliases when creating a new range** (the first `--from A --to B` workspace creation normalizes aliases; both spellings are accepted): e.g. `cts`↔`CTS`, `route`↔`routing`, `timingopt`↔`Timing optimization`, `postlec`↔`postRouteLec`. A misspelled name returns `unknown_step` with the full list of available step names — copy one of them as printed. @@ -328,7 +328,7 @@ ecc run [OPTIONS] --plain key=value output for scripting ``` -For a fresh or `--overwrite` workspace, the pipeline reads `ecc.toml` → resolves only the design files required by the entry step plus PDK/parameters → preflights bundled ecc-tools plus the selected tools → records the workspace in `project.json` → creates it under `/` → copies its declared design inputs to `origin/`, writes the resulting step configuration, and executes the selected flow. A workspace never stores a second project input manifest. Existing workspaces resume their persisted flow without rewriting its inputs or step configuration. `rtl2gds` is the full 15-step chain (Synthesis→LEC (Yosys equivalence check)→Floorplan→place→CTS→legalization→Timing optimization (sizer)→route→filler→RCX→sta→LVS→postRouteLec (Yosys equivalence check)→DRC→Harden; Harden emits GDS + abstract LEF + timing LIB). +For a fresh or `--overwrite` workspace, the pipeline reads `ecc.toml` → resolves only the design files required by the entry step plus PDK/parameters → preflights bundled ecc-tools plus the selected tools → records the workspace in `project.json` → creates it under `/` → copies its declared design inputs to `origin/`, writes the resulting step configuration, and executes the selected flow. A workspace never stores a second project input manifest. Existing workspaces resume their persisted flow without rewriting its inputs or step configuration. `rtl2gds` is the full 17-step chain (Synthesis→LEC (Yosys equivalence check)→preFloorplan→macroPlacement→postFloorplan→place→CTS→legalization→Timing optimization (sizer)→route→filler→RCX→sta→LVS→postRouteLec (Yosys equivalence check)→DRC→Harden; Harden emits GDS + abstract LEF + timing LIB). A summary is printed when the run finishes (real output): @@ -384,7 +384,7 @@ ecc run --workspace cts-only --from cts --to cts # new range workspace (ali ecc run --workspace cts-route --from cts --to routing # same; both ends accept aliases ``` -When creating a range workspace, only the design inputs required by the **entry step** are validated: Synthesis needs `rtl`; LEC/postRouteLec need `netlist` + `golden_netlist`; Floorplan needs `netlist`; the physical steps (place/CTS/legalization/timing optimization/route/filler/rcx/drc/lvs/harden) need `def` + `netlist`; sta additionally needs `spef`; `sdc` is validated only when declared. Missing inputs report `step_input_missing`: +When creating a range workspace, only the design inputs required by the **entry step** are validated: Synthesis needs `rtl`; LEC/postRouteLec need `netlist` + `golden_netlist`; preFloorplan needs `netlist`; macroPlacement, postFloorplan, and the remaining physical steps (place/CTS/legalization/timing optimization/route/filler/rcx/drc/lvs/harden) need `def` + `netlist`; sta additionally needs `spef`; `sdc` is validated only when declared. Missing inputs report `step_input_missing`: ```console $ ecc run --from cts --to route # new range but def/netlist are missing @@ -394,19 +394,19 @@ $ ecc run --from cts --to route # new range but def/netlist are missing rc=1 ``` -For example, to reuse the Floorplan artifacts from existing workspace `2` and create a workspace that runs only placement through routing, declare the **matching pair** of DEF and gate-level netlist as the new workspace's entry inputs before creating the range flow: +For example, to reuse the post-floorplan artifacts from existing workspace `2` and create a workspace that runs only placement through routing, declare the **matching pair** of DEF and gate-level netlist as the new workspace's entry inputs before creating the range flow: ```bash PROJECT=~/projects/benchmark/gcd -SOURCE="$PROJECT/2/Floorplan_ecc/output" +SOURCE="$PROJECT/2/postFloorplan_ecc/output" -ecc project set design.def "$SOURCE/gcd_Floorplan.def.gz" --project "$PROJECT" -ecc project set design.netlist "$SOURCE/gcd_Floorplan.v.gz" --project "$PROJECT" +ecc project set design.def "$SOURCE/gcd_postFloorplan.def.gz" --project "$PROJECT" +ecc project set design.netlist "$SOURCE/gcd_postFloorplan.v.gz" --project "$PROJECT" ecc run --project "$PROJECT" --workspace floorplan-2-place-route \ --from placement --to routing ``` -`ecc run` has no `--def` or `--netlist` flag; a range entry reads `design.def` and `design.netlist` from `ecc.toml`. This registers `floorplan-2-place-route` in `project.json`, copies the two files into the new workspace's `origin/`, and runs placement through routing without rerunning Floorplan. The first two commands change project-level `ecc.toml`, so they also affect later fresh workspaces. If those fields were previously absent, restore the default project entry after creation with `ecc project unset design.def --project "$PROJECT"` and `ecc project unset design.netlist --project "$PROJECT"`. +`ecc run` has no `--def` or `--netlist` flag; a range entry reads `design.def` and `design.netlist` from `ecc.toml`. This registers `floorplan-2-place-route` in `project.json`, copies the two files into the new workspace's `origin/`, and runs placement through routing without rerunning postFloorplan. The first two commands change project-level `ecc.toml`, so they also affect later fresh workspaces. If those fields were previously absent, restore the default project entry after creation with `ecc project unset design.def --project "$PROJECT"` and `ecc project unset design.netlist --project "$PROJECT"`. ### 5.2 Workspace mode (debugging / re-runs) @@ -424,8 +424,8 @@ ecc run [--workspace NAME] [--resume | --from STEP [--to STEP] | --only STEP [-- ```console $ ecc run --workspace default --from synthesis # the persisted name is "Synthesis" [error] - unknown_step unknown step 'synthesis'; available steps: Synthesis, lec, Floorplan, - place, CTS, legalization, Timing optimization, route, filler, RCX, sta, lvs, + unknown_step unknown step 'synthesis'; available steps: Synthesis, lec, preFloorplan, + macroPlacement, postFloorplan, place, CTS, legalization, Timing optimization, route, filler, RCX, sta, lvs, postRouteLec, drc, Harden workspace: /tmp/gcd/default ``` @@ -525,8 +525,12 @@ $ ecc status log: ecc log synthesis --workspace default lec (yosys_lec) success 0:0:1 log: ecc log lec --workspace default - floorplan (ecc) success 0:0:1 - log: ecc log floorplan --workspace default + pre_floorplan (ecc) success 0:0:1 + log: ecc log pre_floorplan --workspace default + macro_placement (dreamplace) success 0:0:5 + log: ecc log macro_placement --workspace default + post_floorplan (ecc) success 0:0:1 + log: ecc log post_floorplan --workspace default placement (dreamplace) incomplete log: ecc log placement --workspace default cts (ecc) unstart @@ -970,18 +974,20 @@ Previews the **current step artifacts** of one workspace directly in the termina - `checklist`: `/checklist.json` (the v3 contract; falls back to `home/checklist.json` filtered by step when missing) - `--section` may be repeated to select sections; a section whose artifacts are missing renders as `unavailable` -Step tokens follow `ecc log` (`synthesis/floorplan/placement/cts/...`); flow-internal names (`Timing optimization`) and directory-name variants (`timing_optimization`) are accepted too. An unknown token returns `unknown_step` with the list of valid tokens. +Step tokens follow `ecc log` (`synthesis/pre_floorplan/macro_placement/post_floorplan/placement/cts/...`); flow-internal names (`Timing optimization`) and directory-name variants (`timing_optimization`) are accepted too. An unknown token returns `unknown_step` with the list of valid tokens. ```console $ ecc report step --workspace default [report step] workspace : /tmp/gcd/default - steps : 15 + steps : 17 step tool status runtime peak MB metrics quality checklist synthesis yosys success 0:0:17 1165.89 10 pass ready lec yosys_lec success 0:0:1 0.164 - - ready - floorplan ecc success 0:0:1 97.516 11 pass ready + pre_floorplan ecc success 0:0:1 97.516 - - ready + macro_placement dreamplace success 0:0:5 97.516 - - ready + post_floorplan ecc success 0:0:1 97.516 11 pass ready ... drc ecc success 0:0:3 42.0 12 blocked blocked (1 blocked) @@ -1079,7 +1085,7 @@ ecc report qor --workspace exp1 # With a ready-made synthesis netlist, a range workspace can also start from an # intermediate step (entry-input requirements in §5.1): -ecc run --workspace pnr --from floorplan --to route +ecc run --workspace pnr --from prefloorplan --to route ``` Once `project.json` exists, project-scoped inspection, signoff, and report commands select among declared workspaces; a single active workspace is auto-selected, while multiple active workspaces require an explicit `--workspace NAME` (otherwise `workspace_required` is reported, listing the available names). A workspace no longer in use can be dropped from auto-selection by changing its `status` to `archived` in `project.json`. diff --git a/chipcompiler/docs/floorplan-flow.cn.md b/chipcompiler/docs/floorplan-flow.cn.md new file mode 100644 index 000000000..9186e783e --- /dev/null +++ b/chipcompiler/docs/floorplan-flow.cn.md @@ -0,0 +1,79 @@ +# 分阶段 Floorplan 流程 + +`rtl2gds` 预设将 floorplan 持久化为三个独立阶段: + +```mermaid +flowchart LR + LEC[LEC] --> PRE[preFloorplan\necc] + PRE --> MACRO[macroPlacement\nDreamPlace] + MACRO --> POST[postFloorplan\necc] + POST --> PLACE[placement\nDreamPlace] +``` + +普通的 `ecc run` 会顺序执行这三个阶段。拆分后,每个阶段都有独立的产物、 +日志和 flow 状态,可单独查看或重跑;这**不**表示当前流程会自动为 GUI 操作暂停。 + +## 三个阶段 + +| 持久化步骤名 | 工具 | workspace 目录 | 工作内容 | +| --- | --- | --- | --- | +| `preFloorplan` | ecc | `preFloorplan_ecc/` | 建立初始 die/core 规划,并启用自动宏摆放。它从共享 floorplan 配置生成 `config/floorplan_ecc_simple.json`。 | +| `macroPlacement` | DreamPlace | `macroPlacement_dreamplace/` | 基于 pre-floorplan 数据库运行仅宏单元摆放,然后写出 Tcl 宏位置交接文件。 | +| `postFloorplan` | ecc | `postFloorplan_ecc/` | 以文件模式读取宏位置交接文件,完成 tracks、IO pin、tap/endcap、PDN、时钟网络设置、输出和分析。 | + +`Floorplan` 仍只是共享配置键,不是可执行的 flow 步骤。在已有 workspace 上选择 +步骤时,请使用上表中的三个持久化名称。 + +## 宏位置交接文件 + +宏单元摆放成功后,流程会写入 `config/macro_localtion.tcl`。文件名保留现有的 +`localtion` 拼写,以保证兼容性。 + +该文件是 macro placement 和 post-floorplan 之间的 iDB/Tcl 交接文件。每个硬宏 +使用如下命令表示,坐标单位为微米: + +```tcl +placeInstance +setInstancePlacementStatus -status fixed -name +``` + +`postFloorplan` 会通过 `floorplan_ecc.json`,以 +`macro_placer.mode = "file"` 将此文件交给 iFP。没有硬宏的设计可以使用空文件; +文件中列出的每个实例都必须存在于当前设计中,旧的四列 location 文本格式不能作为 +交接文件使用。生成的方向值为 `R0`、`R90`、`R180`、`R270`、`MY`、`MX90`、`MX` +和 `MY90`。 + +如果 GUI 或用户提供宏位置,也必须使用同一种 Tcl 格式。重新运行 +`macroPlacement` 会重新生成该文件;使用有效的手工交接文件后,应只运行 +`postFloorplan`,不要再次运行宏摆放阶段。 + +## 恢复与重跑 + +普通恢复会从第一个未成功的持久化步骤继续: + +```bash +ecc run --workspace default --resume +``` + +已有 workspace 中,`--from` 和 `--to` 需要使用持久化的驼峰步骤名。例如: + +```bash +# 重新生成初始规划和宏位置交接文件。 +ecc run --workspace default --from preFloorplan --to macroPlacement + +# 不重跑宏摆放,直接应用现有或手工提供的 Tcl 交接文件。 +ecc run --workspace default --from postFloorplan --to postFloorplan +``` + +重跑某一步会替换该步输出,并把下游步骤标记为需要重新执行。新建范围 workspace +时,`--from` 和 `--to` 必须同时给出,且需要预先声明入口步骤所需的设计输入。 +完整规则见 [CLI 用户指南](ecc-user-guide.cn.md)。 + +## 配置 + +三个 floorplan 阶段共享 workspace 的 `config/floorplan_ecc.json`。 +`preFloorplan` 使用派生出的简单配置,令 `macro_placer.mode = "auto"`; +`postFloorplan` 使用共享配置,并在 `file` 模式下读取宏位置交接文件。 +请通过 `ecc param` 或 `ecc.toml` 参数修改已审核的 floorplan 设置,不要依赖每次 +运行生成的字段。可调参数和 JSON 字段见 +[配置参考](ecc-config-ref.cn.md)。 diff --git a/chipcompiler/docs/floorplan-flow.en.md b/chipcompiler/docs/floorplan-flow.en.md new file mode 100644 index 000000000..faebfc074 --- /dev/null +++ b/chipcompiler/docs/floorplan-flow.en.md @@ -0,0 +1,85 @@ +# Staged Floorplan Flow + +The `rtl2gds` preset persists floorplanning as three separate stages: + +```mermaid +flowchart LR + LEC[LEC] --> PRE[preFloorplan\necc] + PRE --> MACRO[macroPlacement\nDreamPlace] + MACRO --> POST[postFloorplan\necc] + POST --> PLACE[placement\nDreamPlace] +``` + +A normal `ecc run` executes the stages consecutively. They are separate so +each result, log, and flow state can be inspected or re-run independently. +The split does **not** add an automatic GUI pause protocol. + +## Stages + +| Persisted step | Tool | Workspace directory | What it does | +| --- | --- | --- | --- | +| `preFloorplan` | ecc | `preFloorplan_ecc/` | Builds the initial die/core plan with automatic macro placement enabled. It derives `config/floorplan_ecc_simple.json` from the shared floorplan configuration. | +| `macroPlacement` | DreamPlace | `macroPlacement_dreamplace/` | Runs macro-only placement from the pre-floorplan database, then writes the Tcl macro-location handoff. | +| `postFloorplan` | ecc | `postFloorplan_ecc/` | Loads the macro-location handoff in file mode and completes floorplanning: tracks, IO pins, tap/endcap cells, PDN, clock-net setup, outputs, and analysis. | + +`Floorplan` remains the shared configuration key; it is not an executable flow +step. Use the three persisted names above when selecting steps in an existing +workspace. + +## Macro-location handoff + +After successful macro-only placement, the flow writes +`config/macro_localtion.tcl`. The filename intentionally retains the existing +`localtion` spelling for compatibility. + +The file is the iDB/Tcl handoff between macro placement and post-floorplan. +Each hard macro is represented by these commands, with coordinates in microns: + +```tcl +placeInstance +setInstancePlacementStatus -status fixed -name +``` + +`postFloorplan` passes this file to iFP through `floorplan_ecc.json` with +`macro_placer.mode = "file"`. An empty file is valid for a design with no hard +macros. Every listed instance must exist in the design; old four-column +location text files are not valid handoffs. The generated orientation values +are `R0`, `R90`, `R180`, `R270`, `MY`, `MX90`, `MX`, and `MY90`. + +If a GUI or a user supplies macro locations, it must write this same Tcl +format. Re-running `macroPlacement` regenerates the handoff, so run only +`postFloorplan` after replacing it with a valid manual handoff. + +## Resuming or re-running stages + +The normal command resumes from the first non-successful persisted step: + +```bash +ecc run --workspace default --resume +``` + +For an existing workspace, `--from` and `--to` use the persisted camel-case +names. For example: + +```bash +# Recreate the initial plan and macro handoff. +ecc run --workspace default --from preFloorplan --to macroPlacement + +# Apply an existing or manually supplied Tcl handoff without rerunning macros. +ecc run --workspace default --from postFloorplan --to postFloorplan +``` + +Re-running a stage replaces that stage's output and marks downstream stages +for re-execution. On a new range workspace, `--from` and `--to` must be used +together and the entry step's required design inputs must already be declared. +See the [CLI User Guide](ecc-user-guide.en.md) for the full selector rules. + +## Configuration + +All floorplan stages share the workspace's `config/floorplan_ecc.json`. +`preFloorplan` uses a generated simple copy with `macro_placer.mode = "auto"`; +`postFloorplan` uses the shared file with the macro handoff in `file` mode. +Use `ecc param` or `ecc.toml` parameters to change reviewed floorplan settings +rather than relying on generated per-run values. The +[Configuration Reference](ecc-config-ref.en.md) lists the supported floorplan +parameters and JSON fields. diff --git a/chipcompiler/engine/flow.py b/chipcompiler/engine/flow.py index 63ed79919..dd31da520 100644 --- a/chipcompiler/engine/flow.py +++ b/chipcompiler/engine/flow.py @@ -74,7 +74,9 @@ def _validate_transition(old_state: str | None, new_state: str, step_name: str, _GEOMETRY_SNAPSHOT_STEPS = frozenset( { - StepEnum.FLOORPLAN.value, + StepEnum.PRE_FLOORPLAN.value, + StepEnum.MACRO_PLACEMENT.value, + StepEnum.POST_FLOORPLAN.value, StepEnum.PLACEMENT.value, StepEnum.CTS.value, StepEnum.TIMING_OPT.value, @@ -108,7 +110,9 @@ def build_default_steps(self): steps.append( self.init_flow_step(StepEnum.LEC, "yosys_lec", StateEnum.Unstart, info=lec_info) ) - steps.append(self.init_flow_step(StepEnum.FLOORPLAN, "ecc", StateEnum.Unstart)) + steps.append(self.init_flow_step(StepEnum.PRE_FLOORPLAN, "ecc", StateEnum.Unstart)) + steps.append(self.init_flow_step(StepEnum.MACRO_PLACEMENT, "dreamplace", StateEnum.Unstart)) + steps.append(self.init_flow_step(StepEnum.POST_FLOORPLAN, "ecc", StateEnum.Unstart)) steps.append(self.init_flow_step(StepEnum.PLACEMENT, "dreamplace", StateEnum.Unstart)) steps.append(self.init_flow_step(StepEnum.CTS, "ecc", StateEnum.Unstart)) steps.append(self.init_flow_step(StepEnum.LEGALIZATION, "dreamplace", StateEnum.Unstart)) diff --git a/chipcompiler/engine/qor_report.py b/chipcompiler/engine/qor_report.py index 9e73ef92e..dbfac6069 100644 --- a/chipcompiler/engine/qor_report.py +++ b/chipcompiler/engine/qor_report.py @@ -26,7 +26,7 @@ # GUI FlowStep label for each canonical step that owns a scored directory. _STEP_ENUM_TO_LABEL = { StepEnum.SYNTHESIS.value: "Synth", - StepEnum.FLOORPLAN.value: "Floor", + StepEnum.POST_FLOORPLAN.value: "PostFloorplan", StepEnum.PLACEMENT.value: "Place", StepEnum.CTS.value: "CTS", StepEnum.LEGALIZATION.value: "Legal", diff --git a/chipcompiler/engine/reconcile.py b/chipcompiler/engine/reconcile.py index 3aad977a1..addf79db3 100644 --- a/chipcompiler/engine/reconcile.py +++ b/chipcompiler/engine/reconcile.py @@ -27,12 +27,6 @@ - ``mismatch``: divergent flows — validation is pure-read, nothing was written; the caller surfaces flow_mismatch. -Ledgers persisted in the pre-reorder chain order (DRC/LVS before filler, -RCX/STA/Harden as preset suffixes) are migrated onto the canonical chain by -step name instead of rejected: records up to ``route`` keep their states, -everything after restarts from Unstart, and the outcome falls back to -``repaired``/``resume``. - :func:`classify_workspace` exposes the pure-read phase to callers that must reject a mismatch before loading (and thereby initializing or migrating) the workspace; it additionally returns ``pending_mutation`` @@ -101,46 +95,6 @@ def _is_legacy_missing_synthesis_lec( ) -# The pre-reorder canonical chain: DRC/LVS ran before filler, and RCX/STA/ -# Harden were preset-only suffixes. Ledgers persisted in this shape map onto -# the current chain by step name. -_LEGACY_RTL2GDS_CHAIN = ( - ("Synthesis", "yosys"), - ("lec", "yosys_lec"), - ("Floorplan", "ecc"), - ("place", "dreamplace"), - ("CTS", "ecc"), - ("legalization", "dreamplace"), - ("Timing optimization", "sizer"), - ("route", "ecc"), - ("drc", "ecc"), - ("lvs", "ecc"), - ("filler", "ecc"), - ("postRouteLec", "yosys_lec"), - ("RCX", "ecc"), - ("sta", "ecc"), - ("Harden", "ecc"), -) - - -def _is_legacy_reordered_chain(persisted: list[tuple[str, str]]) -> bool: - """Whether a ledger is an order-preserving slice of the pre-reorder chain. - - Legacy presets always began at Synthesis, so a ledger starting anywhere - else is a foreign shape, not a legacy one. - """ - if not persisted or persisted[0] != _LEGACY_RTL2GDS_CHAIN[0]: - return False - positions = {entry: index for index, entry in enumerate(_LEGACY_RTL2GDS_CHAIN)} - taken = [] - for entry in persisted: - position = positions.get(entry) - if position is None: - return False - taken.append(position) - return taken == sorted(set(taken)) and len(taken) == len(set(taken)) - - def _target_entries(flow_section: dict) -> list[tuple[str, str]]: """(name, tool) entries for a [flow] section, over the canonical chain.""" from chipcompiler.data.workspace import _canonical_rtl2gds_flow_entries @@ -245,8 +199,6 @@ def _probe_workspace(workspace_dir: Path, target_section: dict | None): relation = compare_flows(persisted, target) if relation == "divergent" and _is_legacy_missing_synthesis_lec(persisted, target): relation = "legacy_missing_synthesis_lec" - if relation == "divergent" and _is_legacy_reordered_chain(persisted): - relation = "legacy_reordered_chain" if relation == "divergent": return ( ReconcileResult( @@ -262,10 +214,7 @@ def _probe_workspace(workspace_dir: Path, target_section: dict | None): stale = flow_range_of(workspace_flow) != flow_range_of(target_section) else: stale = bool(target_section) - if ( - relation in {"proper_prefix", "legacy_missing_synthesis_lec", "legacy_reordered_chain"} - or stale - ): + if relation in {"proper_prefix", "legacy_missing_synthesis_lec"} or stale: context = { "flow_data": flow_data, "persisted": persisted, @@ -434,40 +383,6 @@ def _apply_mutation(workspace_dir: Path, probe: ReconcileResult, context: dict) ) adopted_flow = dict(target_section) outcome = "extended" - elif relation == "legacy_reordered_chain": - # Map a pre-reorder ledger onto the canonical chain by step name: - # the Synthesis..route prefix is identical in both orders and keeps - # its records; every post-route step ran with pre-reorder inputs - # (DRC/LVS before filler), so it restarts from Unstart. - import copy - - from chipcompiler.data.workspace import ( - _canonical_rtl2gds_flow_entries, - _flow_step_template, - ) - - context["flow_data_original"] = copy.deepcopy(flow_data) - persisted_by_name = { - step.get("name"): step for step in flow_data.get("steps", []) if isinstance(step, dict) - } - chain_names = [name for name, _tool, _state in _canonical_rtl2gds_flow_entries()] - route_position = chain_names.index("route") - steps = [] - for name, tool in target: - old = persisted_by_name.get(name) - if old is not None and chain_names.index(name) <= route_position: - steps.append(old) - else: - steps.append(_flow_step_template(name, tool, "Unstart")) - if name not in persisted_by_name: - appended.append(name) - flow_data["steps"] = steps - if not json_write(workspace_dir / "home" / "flow.json", flow_data): - return ReconcileResult( - outcome="mismatch", - error=f"failed to rewrite flow steps into {workspace_dir / 'home' / 'flow.json'}", - ) - adopted_flow = dict(target_section) else: # Adopt the effective target when the persisted [flow] is stale # (crash between append and adopt, a hand-edited file, or an @@ -484,8 +399,6 @@ def _apply_mutation(workspace_dir: Path, probe: ReconcileResult, context: dict) # failure is an error, not a tolerated partial state. Roll the # ledger back too — leaving the appended suffix behind would # report failure while the persisted flow is wider than the target. - # The reordered-chain migration can rewrite the ledger without - # appending a single name, so the rollback keys on the snapshot. if "flow_data_original" in context: json_write(workspace_dir / "home" / "flow.json", context["flow_data_original"]) return ReconcileResult( diff --git a/chipcompiler/engine/rerun.py b/chipcompiler/engine/rerun.py index 00c425ce8..4de57a694 100644 --- a/chipcompiler/engine/rerun.py +++ b/chipcompiler/engine/rerun.py @@ -130,8 +130,8 @@ def _require_step_index(flow: "EngineFlow", name: str) -> int: for index, step in enumerate(steps): if step.get("name") == name: return index - # Selectors accept any CLI spelling of a persisted step ("floorplan", - # "Floorplan", "FLOORPLAN", "synth", ...) — only an unambiguous + # Selectors accept any CLI spelling of a persisted step ("postfloorplan", + # "postFloorplan", "POSTFLOORPLAN", "synth", ...) — only an unambiguous # canonical form may not be guessed at. from chipcompiler.rtl2gds.builder import normalize_flow_step diff --git a/chipcompiler/engine/signoff/report_data.py b/chipcompiler/engine/signoff/report_data.py index 17dc67e1c..ceedb60a7 100644 --- a/chipcompiler/engine/signoff/report_data.py +++ b/chipcompiler/engine/signoff/report_data.py @@ -12,10 +12,15 @@ "synth": "Synth", "synthesis_yosys": "Synth", "yosys": "Synth", - "floorplan": "Floor", - "floor": "Floor", - "floorplan_ecc": "Floor", - "macro_placement": "Floor", + "prefloorplan": "PreFloorplan", + "pre_floorplan": "PreFloorplan", + "prefloorplan_ecc": "PreFloorplan", + "macroplacement": "MacroPlacement", + "macro_placement": "MacroPlacement", + "macroplacement_dreamplace": "MacroPlacement", + "postfloorplan": "PostFloorplan", + "post_floorplan": "PostFloorplan", + "postfloorplan_ecc": "PostFloorplan", "lec": "LEC", "postroutelec": "LEC", "postroutelec_yosys_lec": "LEC", diff --git a/chipcompiler/engine/signoff/report_sections.py b/chipcompiler/engine/signoff/report_sections.py index 93ef7f8ec..cd9bda09b 100644 --- a/chipcompiler/engine/signoff/report_sections.py +++ b/chipcompiler/engine/signoff/report_sections.py @@ -23,7 +23,7 @@ parse_runtime_seconds, ) -_AREA_STAGES = ["Harden", "Route", "Legal", "Place", "Floor"] +_AREA_STAGES = ["Harden", "Route", "Legal", "Place", "PostFloorplan"] def _normalize_area(value): @@ -104,19 +104,28 @@ def _extract_physical(q, warnings) -> PhysicalMetrics: macro_area_um2=q( "Physical", "Macro Area", - ["Harden", "Route", "Place", "Floor"], + ["Harden", "Route", "Place", "PostFloorplan"], ["Instances.macros.area", "macro_area", "macro_area_um2", "macroArea"], )[0], macro_count=q( "Physical", "Macro Count", - ["Harden", "Route", "Place", "Floor"], + ["Harden", "Route", "Place", "PostFloorplan"], ["Instances.macros.num", "macro_count", "num_macros", "macroCount"], )[0], instance_count=q( "Physical", "Total Instances", - ["Harden", "Route", "Legal", "Place", "CTS", "Fanout", "Floor", "Synth"], + [ + "Harden", + "Route", + "Legal", + "Place", + "CTS", + "Fanout", + "PostFloorplan", + "Synth", + ], [ "Design Statis.num_instances", "Instances.total.num", @@ -156,7 +165,7 @@ def _extract_physical(q, warnings) -> PhysicalMetrics: io_pin_count=q( "Physical", "IO Pin Count", - ["Harden", "Route", "Floor", "Synth"], + ["Harden", "Route", "PostFloorplan", "Synth"], [ "Design Statis.num_iopins", "Instances.total.pin_num", @@ -171,7 +180,16 @@ def _extract_physical(q, warnings) -> PhysicalMetrics: net_count=q( "Physical", "Total Nets", - ["Harden", "Route", "Legal", "Place", "CTS", "Fanout", "Floor", "Synth"], + [ + "Harden", + "Route", + "Legal", + "Place", + "CTS", + "Fanout", + "PostFloorplan", + "Synth", + ], [ "Design Statis.num_nets", "design.num_wires", @@ -208,7 +226,7 @@ def _extract_routing(q) -> RoutingMetrics: hpwl_um=q( "Routing", "Half-Perimeter Wirelength", - ["Place", "Floor"], + ["Place", "PostFloorplan"], ["place_hpwl", "hpwl", "hpwl_um", "half_perimeter_wirelength"], )[0], estimated_wirelength_um=q( diff --git a/chipcompiler/engine/signoff/report_timing.py b/chipcompiler/engine/signoff/report_timing.py index 152579e79..c6d248eb6 100644 --- a/chipcompiler/engine/signoff/report_timing.py +++ b/chipcompiler/engine/signoff/report_timing.py @@ -424,7 +424,7 @@ def _extract_clock(q) -> ClockMetrics: clock_buffer_count = q( "Clock", "Clock Buffer Count", - ["CTS", "Route", "Place", "Floor"], + ["CTS", "Route", "Place", "PostFloorplan"], [ "cts_buffer_count", "CTS.buffer_num", diff --git a/chipcompiler/rtl2gds/builder.py b/chipcompiler/rtl2gds/builder.py index a1efd40fc..a3020ac3a 100644 --- a/chipcompiler/rtl2gds/builder.py +++ b/chipcompiler/rtl2gds/builder.py @@ -9,7 +9,9 @@ def build_rtl2gds_flow() -> list: steps.append((StepEnum.SYNTHESIS, "yosys", StateEnum.Unstart)) steps.append((StepEnum.LEC, "yosys_lec", StateEnum.Unstart)) - steps.append((StepEnum.FLOORPLAN, "ecc", StateEnum.Unstart)) + steps.append((StepEnum.PRE_FLOORPLAN, "ecc", StateEnum.Unstart)) + steps.append((StepEnum.MACRO_PLACEMENT, "dreamplace", StateEnum.Unstart)) + steps.append((StepEnum.POST_FLOORPLAN, "ecc", StateEnum.Unstart)) steps.append((StepEnum.PLACEMENT, "dreamplace", StateEnum.Unstart)) steps.append((StepEnum.CTS, "ecc", StateEnum.Unstart)) steps.append((StepEnum.LEGALIZATION, "dreamplace", StateEnum.Unstart)) @@ -37,8 +39,10 @@ def normalize_flow_step(value: str | StepEnum) -> str: aliases = { "synth": StepEnum.SYNTHESIS.value, "synthesis": StepEnum.SYNTHESIS.value, - "floor": StepEnum.FLOORPLAN.value, - "floorplan": StepEnum.FLOORPLAN.value, + "prefloorplan": StepEnum.PRE_FLOORPLAN.value, + "macroplace": StepEnum.MACRO_PLACEMENT.value, + "macroplacement": StepEnum.MACRO_PLACEMENT.value, + "postfloorplan": StepEnum.POST_FLOORPLAN.value, "place": StepEnum.PLACEMENT.value, "placement": StepEnum.PLACEMENT.value, "cts": StepEnum.CTS.value, diff --git a/chipcompiler/runtime/workspace_api.py b/chipcompiler/runtime/workspace_api.py index ca72b9127..5c256c221 100644 --- a/chipcompiler/runtime/workspace_api.py +++ b/chipcompiler/runtime/workspace_api.py @@ -1870,7 +1870,7 @@ def _apply_layout_edit_workspace_staging(workspace, workspace_staging: dict[str, def _mark_placement_and_later_stale(flow_data: dict[str, Any], step_name: str) -> bool: - if step_name != "Floorplan": + if step_name != "postFloorplan": return False steps = flow_data.get("steps") if not isinstance(steps, list): diff --git a/chipcompiler/thirdparty/ecc-dreamplace b/chipcompiler/thirdparty/ecc-dreamplace index 308dcd35d..77e343ca9 160000 --- a/chipcompiler/thirdparty/ecc-dreamplace +++ b/chipcompiler/thirdparty/ecc-dreamplace @@ -1 +1 @@ -Subproject commit 308dcd35da567d56301fcbb3a6b22018a2d4fec4 +Subproject commit 77e343ca9eab9e79b429ca1a4cb82d9c328fa9c3 diff --git a/chipcompiler/thirdparty/ecc-tools b/chipcompiler/thirdparty/ecc-tools index efbca3356..fdaa70a97 160000 --- a/chipcompiler/thirdparty/ecc-tools +++ b/chipcompiler/thirdparty/ecc-tools @@ -1 +1 @@ -Subproject commit efbca335661a1547202abbde40855441da7766dd +Subproject commit fdaa70a971be6c33a8dc6d695011bd9854ee18c5 diff --git a/chipcompiler/tools/ecc/builder.py b/chipcompiler/tools/ecc/builder.py index 28c823a85..2b756bf0f 100644 --- a/chipcompiler/tools/ecc/builder.py +++ b/chipcompiler/tools/ecc/builder.py @@ -86,7 +86,9 @@ def build_step( data=EccData( dir=data_dir, steps={ - StepEnum.FLOORPLAN.value: data_dir / "fp", + StepEnum.PRE_FLOORPLAN.value: data_dir / "fp", + StepEnum.MACRO_PLACEMENT.value: data_dir / "macro", + StepEnum.POST_FLOORPLAN.value: data_dir / "fp", StepEnum.PLACEMENT.value: data_dir / "pl", StepEnum.LEGALIZATION.value: data_dir / "pl", StepEnum.FILLER.value: data_dir / "pl", diff --git a/chipcompiler/tools/ecc/checklist.py b/chipcompiler/tools/ecc/checklist.py index f6bc3d523..4429c7baa 100644 --- a/chipcompiler/tools/ecc/checklist.py +++ b/chipcompiler/tools/ecc/checklist.py @@ -11,19 +11,6 @@ class EccChecklist: CHECKLIST_ITEMS = { - StepEnum.FLOORPLAN: [ - ("Area", "check DIE area"), - ("Area", "check core area"), - ("Area", "check core utilization"), - ("Rows/Tracks", "check placement rows and sites"), - ("Rows/Tracks", "check routing tracks"), - ("Pins/Macros", "check IO pin placement"), - ("Pins/Macros", "check macro placement"), - ("PDN", "check tap and endcap insertion"), - ("PDN", "check PDN IO and global connect"), - ("PDN", "check PDN grid and stripes"), - ("Clock", "check clock net type"), - ], StepEnum.PLACEMENT: [ ("Density", "check target density"), ("Density", "check placement overflow"), @@ -185,148 +172,6 @@ def apply_checks( return all(success or item in warnings for _, item, success, _ in checks) -class EccFloorplanChecklist(EccChecklist): - def check(self) -> bool: - return refresh_step_checklist(self.workspace, self.workspace_step) - - step = StepEnum.FLOORPLAN.value - metrics = self.qor_metrics() - db = json_read(self.workspace_step.feature.db or "") - subflow = json_read(self.workspace_step.subflow.path or "") - - try: - with open( - self.workspace_step.log.file or "", - encoding="utf-8", - errors="ignore", - ) as file: - log_text = file.read() - except OSError: - log_text = "" - - layout = db.get("Design Layout", {}) - statis = db.get("Design Statis", {}) - layers = db.get("Layers", {}) - nets = db.get("Nets", {}) - subflow_state = {item.get("name"): item.get("state") for item in subflow.get("steps", [])} - output_success = all( - [ - self.check_file(self.workspace_step.output.def_ or ""), - self.check_file(self.workspace_step.output.verilog or ""), - self.check_file(self.workspace_step.output.gds or ""), - ] - ) - - die_area, die_area_error = metrics.number("die_area") - die_width, die_width_error = metrics.number("die_width") - die_height, die_height_error = metrics.number("die_height") - core_area_metric, core_area_error = metrics.number("core_area") - core_util, core_util_error = metrics.number("core_utilization") - num_iopins, io_pin_error = metrics.number("io_pin_count") - core_area = self.to_float(layout.get("core_area"), 0.0) - core_width = self.to_float(layout.get("core_bounding_width"), 0.0) - core_height = self.to_float(layout.get("core_bounding_height"), 0.0) - num_pdn = self.to_float(statis.get("num_pdn"), 0.0) - num_clock = self.to_float(nets.get("num_clock"), 0.0) - num_routing_layers = self.to_float( - layers.get("num_layers_routing", statis.get("num_layers_routing")), - 0.0, - ) - - checks = [ - ( - "Area", - "check DIE area", - die_area is not None - and die_width is not None - and die_height is not None - and die_area > 0 - and die_width > 0 - and die_height > 0, - die_area_error - or die_width_error - or die_height_error - or "die_area, die_width, and die_height must be greater than zero", - ), - ( - "Area", - "check core area", - core_area_metric is not None - and core_area_metric > 0 - and core_area > 0 - and core_width > 0 - and core_height > 0, - core_area_error - or "core_area must be positive and floorplan feature must define core bounds", - ), - ( - "Area", - "check core utilization", - core_util is not None and 0 < core_util <= 1, - core_util_error or f"core_utilization must be within (0, 1], got {core_util}", - ), - ( - "Rows/Tracks", - "check placement rows and sites", - subflow_state.get("init floorplan") == "Success" - and "Write ROWS success" in log_text - and output_success, - "Floorplan rows/sites were not written or DEF/verilog/GDS output is missing", - ), - ( - "Rows/Tracks", - "check routing tracks", - subflow_state.get("create tracks") == "Success" - and num_routing_layers > 0 - and "Write Track Grid success" in log_text, - "Routing track subflow, routing layers, or track-grid log evidence is missing", - ), - ( - "Pins/Macros", - "check IO pin placement", - subflow_state.get("place io pins") == "Success" - and num_iopins is not None - and num_iopins > 0 - and "Write PINS success" in log_text, - io_pin_error or "IO pin placement subflow or PINS output evidence is missing", - ), - ( - "Pins/Macros", - "check macro placement", - "Macros" in db and "Macros Statis" in db and output_success, - "Macro feature sections or floorplan output files are missing", - ), - ( - "PDN", - "check tap and endcap insertion", - subflow_state.get("tap cell") == "Success" - and "Write COMPONENTS success" in log_text, - "Tap-cell subflow or COMPONENTS output evidence is missing", - ), - ( - "PDN", - "check PDN IO and global connect", - subflow_state.get("PDN") == "Success" and num_pdn >= 2, - f"PDN subflow is incomplete or feature reports only {num_pdn} PDN entries", - ), - ( - "PDN", - "check PDN grid and stripes", - subflow_state.get("PDN") == "Success" and "Write SPECIALNETS success" in log_text, - "PDN subflow or SPECIALNETS output evidence is missing", - ), - ( - "Clock", - "check clock net type", - subflow_state.get("set clock net") == "Success" and num_clock > 0, - f"Clock-net subflow is incomplete or feature reports {num_clock} clock nets", - ), - ] - - warning_items = {"check macro placement"} - return self.apply_checks(step, checks, warning_items) - - class EccCtsChecklist(EccChecklist): def check(self) -> bool: return refresh_step_checklist(self.workspace, self.workspace_step) diff --git a/chipcompiler/tools/ecc/configs/macro_localtion.tcl b/chipcompiler/tools/ecc/configs/macro_localtion.tcl new file mode 100644 index 000000000..dcc6b7f9f --- /dev/null +++ b/chipcompiler/tools/ecc/configs/macro_localtion.tcl @@ -0,0 +1,3 @@ +# Hard-macro placement commands generated by saveMacroTCL(). +# placeInstance +# setInstancePlacementStatus -status fixed -name diff --git a/chipcompiler/tools/ecc/metrics.py b/chipcompiler/tools/ecc/metrics.py index fe067833f..efa854cf0 100644 --- a/chipcompiler/tools/ecc/metrics.py +++ b/chipcompiler/tools/ecc/metrics.py @@ -560,7 +560,7 @@ "synthesis_power_dynamic_uw", "synthesis_power_leakage_uw", ], - StepEnum.FLOORPLAN.value: [ + StepEnum.POST_FLOORPLAN.value: [ "die_area", "die_width", "die_height", @@ -1709,7 +1709,7 @@ def _metric_scope_and_roles(step: WorkspaceStep, metric_id: str) -> tuple[str, s if step.name == StepEnum.SYNTHESIS.value: scope = "synthesis" step_role = "primary" - elif step.name == StepEnum.FLOORPLAN.value: + elif step.name == StepEnum.POST_FLOORPLAN.value: scope = "floorplan" step_role = "primary" elif step.name == StepEnum.PLACEMENT.value: @@ -2820,7 +2820,7 @@ def _gate_state(*, available: bool, passed: bool) -> str: _MPC_AREA_SOURCE_STEPS = { - StepEnum.FLOORPLAN.value, + StepEnum.POST_FLOORPLAN.value, StepEnum.PLACEMENT.value, StepEnum.CTS.value, StepEnum.LEGALIZATION.value, @@ -3399,7 +3399,7 @@ def build_step_metrics( # step matrics metrics = None match step.name: - case StepEnum.FLOORPLAN.value: + case StepEnum.POST_FLOORPLAN.value: metrics = build_metrics_floorplan(workspace, step) case StepEnum.PLACEMENT.value: metrics = build_metrics_placement(workspace, step) diff --git a/chipcompiler/tools/ecc/module.py b/chipcompiler/tools/ecc/module.py index 4c952737e..78ddf590b 100644 --- a/chipcompiler/tools/ecc/module.py +++ b/chipcompiler/tools/ecc/module.py @@ -242,9 +242,9 @@ def gds_save(self, output_path: PathArg, *, is_harden: bool = False): """save gds file""" self.ecc.gds_save(path_text(output_path), is_harden) - def tcl_save(self, output_path: str): - """save tcl file""" - self.ecc.tcl_save(path_text(output_path)) + def tcl_save(self, output_path: PathArg) -> bool: + """Save hard-macro placement commands in Tcl format.""" + return self.ecc.tcl_save(path_text(output_path)) def verilog_save(self, output_verilog, cell_names: set | None = None): """verilog save""" @@ -518,6 +518,9 @@ def destroy_lvs(self): def init_fp(self, config: str): return self.ecc.init_fp(config=path_text(config)) + def run_simple_fp(self): + return self.ecc.run_simple_fp() + def run_fp(self): return self.ecc.run_fp() diff --git a/chipcompiler/tools/ecc/plot.py b/chipcompiler/tools/ecc/plot.py index f20414c2e..cfc146102 100644 --- a/chipcompiler/tools/ecc/plot.py +++ b/chipcompiler/tools/ecc/plot.py @@ -21,7 +21,7 @@ def __init__(self, workspace: Workspace, step: EccStep): def plot(self) -> bool: state = True match self.step.name: - case StepEnum.FLOORPLAN.value: + case StepEnum.POST_FLOORPLAN.value: state = state & self.default_plot() case StepEnum.PLACEMENT.value: state = state & self.default_plot() & self.plot_placement_heatmap() diff --git a/chipcompiler/tools/ecc/runner.py b/chipcompiler/tools/ecc/runner.py index 948694201..e57140126 100644 --- a/chipcompiler/tools/ecc/runner.py +++ b/chipcompiler/tools/ecc/runner.py @@ -31,11 +31,13 @@ ) from chipcompiler.tools.ecc.subflow import EccSubFlow, EccSubFlowEnum from chipcompiler.tools.ecc.utility import is_eda_exist -from chipcompiler.utility import json_read +from chipcompiler.utility import json_read, json_write _GEOMETRY_SNAPSHOT_STEPS = frozenset( { - StepEnum.FLOORPLAN.value, + StepEnum.PRE_FLOORPLAN.value, + StepEnum.MACRO_PLACEMENT.value, + StepEnum.POST_FLOORPLAN.value, StepEnum.PLACEMENT.value, StepEnum.CTS.value, StepEnum.TIMING_OPT.value, @@ -476,8 +478,10 @@ def run_step(workspace: Workspace, step: EccStep, ecc_module: ECCToolsModule | N state = False match step.name: - case StepEnum.FLOORPLAN.value: - state = run_floorplan(workspace=workspace, step=step, ecc_module=ecc_module) + case StepEnum.PRE_FLOORPLAN.value: + state = run_pre_floorplan(workspace=workspace, step=step, ecc_module=ecc_module) + case StepEnum.POST_FLOORPLAN.value: + state = run_post_floorplan(workspace=workspace, step=step, ecc_module=ecc_module) case StepEnum.CTS.value: state = run_cts(workspace=workspace, step=step, ecc_module=ecc_module) case StepEnum.ROUTING.value: @@ -710,12 +714,10 @@ def run_filler( return reslut -def run_floorplan( +def run_pre_floorplan( workspace: Workspace, step: EccStep, ecc_module: ECCToolsModule | None = None ) -> bool: - """ - run floorplan - """ + """Run the simple floorplan with automatic macro placement enabled.""" reslut = False sub_flow = EccSubFlow(workspace=workspace, workspace_step=step) @@ -724,9 +726,54 @@ def run_floorplan( if ecc_module is not None: sub_flow.update_step(step_name=EccSubFlowEnum.load_data.value, state=StateEnum.Success) - ecc_module.init_fp(config=workspace.config.get(StepEnum.FLOORPLAN.value, "")) + floorplan_config = os.fspath(workspace.config.get(StepEnum.FLOORPLAN.value, "")) + floorplan_path = Path(floorplan_config) + simple_floorplan_config = os.fspath( + floorplan_path.with_stem(f"{floorplan_path.stem}_simple") + ) + simple_floorplan = json_read(floorplan_config) + simple_floorplan["macro_placer"]["mode"] = "auto" + simple_floorplan["macro_placer"]["file_path"] = "" + json_write(simple_floorplan_config, simple_floorplan) + + ecc_module.init_fp(config=simple_floorplan_config) + ecc_module.run_simple_fp() + ecc_module.destroy_fp() sub_flow.update_step(step_name=EccSubFlowEnum.init_floorplan.value, state=StateEnum.Success) + reslut = save_data( + workspace=workspace, + step=step, + ecc_module=ecc_module, + feature_step=False, + report_timing=False, + ) + sub_flow.update_step(step_name=EccSubFlowEnum.save_data.value, state=StateEnum.Success) + + return reslut + + +def run_post_floorplan( + workspace: Workspace, step: EccStep, ecc_module: ECCToolsModule | None = None +) -> bool: + """Run complete floorplanning with the macro-location file.""" + reslut = False + sub_flow = EccSubFlow(workspace=workspace, workspace_step=step) + + ecc_module = get_eda_instance(workspace=workspace, step=step, ecc_module=ecc_module) + + if ecc_module is not None: + sub_flow.update_step(step_name=EccSubFlowEnum.load_data.value, state=StateEnum.Success) + + floorplan_config = os.fspath(workspace.config.get(StepEnum.FLOORPLAN.value, "")) + floorplan = json_read(floorplan_config) + floorplan["macro_placer"]["mode"] = "file" + floorplan["macro_placer"]["file_path"] = os.fspath( + workspace.config.get("macro_location", "") + ) + json_write(floorplan_config, floorplan) + + ecc_module.init_fp(config=floorplan_config) ecc_module.run_fp() sub_flow.update_step(step_name=EccSubFlowEnum.create_tracks.value, state=StateEnum.Success) sub_flow.update_step(step_name=EccSubFlowEnum.place_io_pins.value, state=StateEnum.Success) @@ -743,7 +790,6 @@ def run_floorplan( feature_step=False, report_timing=False, ) - sub_flow.update_step(step_name=EccSubFlowEnum.save_data.value, state=StateEnum.Success) run_analysis(workspace=workspace, step=step, subflow=sub_flow) diff --git a/chipcompiler/tools/ecc/service.py b/chipcompiler/tools/ecc/service.py index d710aaa75..988e74eae 100644 --- a/chipcompiler/tools/ecc/service.py +++ b/chipcompiler/tools/ecc/service.py @@ -73,8 +73,13 @@ def build_subflow(workspace: Workspace, step: EccStep) -> dict: def build_config(workspace: Workspace, step: EccStep) -> dict: cfg = workspace.config or {} + config_key = ( + StepEnum.FLOORPLAN.value + if step.name in {StepEnum.PRE_FLOORPLAN.value, StepEnum.POST_FLOORPLAN.value} + else step.name + ) info = { - "config": cfg.get(f"{step.name}", ""), + "config": cfg.get(config_key, ""), } return info @@ -99,7 +104,7 @@ def build_maps(workspace: Workspace, step: EccStep) -> dict: info = {} match StepEnum(step.name): - case StepEnum.FLOORPLAN: + case StepEnum.PRE_FLOORPLAN | StepEnum.POST_FLOORPLAN: pass case StepEnum.PLACEMENT: info.update(build_maps_congestion(workspace, step)) diff --git a/chipcompiler/tools/ecc/subflow.py b/chipcompiler/tools/ecc/subflow.py index 626ed157c..90af7c3b4 100644 --- a/chipcompiler/tools/ecc/subflow.py +++ b/chipcompiler/tools/ecc/subflow.py @@ -10,6 +10,7 @@ class EccSubFlowEnum(Enum): save_data = "save data" analysis = "analysis" init_floorplan = "init floorplan" + macro_place = "macro placement" create_tracks = "create tracks" place_io_pins = "place io pins" tap_cell = "tap cell" @@ -66,9 +67,16 @@ def subflow_template(step_name: str): step = StepEnum(self.workspace_step.name) match step: - case StepEnum.FLOORPLAN: + case StepEnum.PRE_FLOORPLAN: steps.append(subflow_template(EccSubFlowEnum.load_data.value)) steps.append(subflow_template(EccSubFlowEnum.init_floorplan.value)) + steps.append(subflow_template(EccSubFlowEnum.save_data.value)) + case StepEnum.MACRO_PLACEMENT: + steps.append(subflow_template(EccSubFlowEnum.load_data.value)) + steps.append(subflow_template(EccSubFlowEnum.macro_place.value)) + steps.append(subflow_template(EccSubFlowEnum.save_data.value)) + case StepEnum.POST_FLOORPLAN: + steps.append(subflow_template(EccSubFlowEnum.load_data.value)) steps.append(subflow_template(EccSubFlowEnum.create_tracks.value)) steps.append(subflow_template(EccSubFlowEnum.place_io_pins.value)) steps.append(subflow_template(EccSubFlowEnum.tap_cell.value)) diff --git a/chipcompiler/tools/ecc_dreamplace/configs/dreamplace_ecc.json b/chipcompiler/tools/ecc_dreamplace/configs/dreamplace_ecc.json index 61a2df49b..c9695912a 100644 --- a/chipcompiler/tools/ecc_dreamplace/configs/dreamplace_ecc.json +++ b/chipcompiler/tools/ecc_dreamplace/configs/dreamplace_ecc.json @@ -56,6 +56,7 @@ "dump_legalize_solution_flag": 0, "routability_opt_flag": 1, "macro_place_flag": 0, + "macro_only": 0, "use_bb": 0, "route_num_bins_x": 512, "route_num_bins_y": 512, diff --git a/chipcompiler/tools/ecc_dreamplace/module.py b/chipcompiler/tools/ecc_dreamplace/module.py index d5433333e..bb6d2372d 100644 --- a/chipcompiler/tools/ecc_dreamplace/module.py +++ b/chipcompiler/tools/ecc_dreamplace/module.py @@ -5,6 +5,7 @@ import os import sys from contextlib import contextmanager +from enum import Enum from pathlib import Path from chipcompiler.data import StepEnum, Workspace, WorkspaceStep @@ -19,6 +20,12 @@ ) +class DreamplaceRunMode(Enum): + PLACEMENT = "placement" + MACRO_PLACEMENT = "macro_placement" + LEGALIZATION = "legalization" + + class DreamplaceModule: def __init__( self, @@ -40,7 +47,7 @@ def __init__( self.param_path = workspace.config["dreamplace"] self.result_dir = str(step.data.workdir_for(step.name)) - def _build_params(self, params_cls, *, legalize_only: bool): + def _build_params(self, params_cls, *, mode: DreamplaceRunMode): with open(self.param_path, encoding="utf-8") as f_reader: config = json.load(f_reader) @@ -58,7 +65,19 @@ def _build_params(self, params_cls, *, legalize_only: bool): params.timing_eval_flag = 0 params.differentiable_timing_obj = 0 - if legalize_only: + params.macro_only = 0 + if mode is DreamplaceRunMode.MACRO_PLACEMENT: + params.macro_only = 1 + params.global_place_flag = 1 + params.macro_place_flag = 1 + params.legalize_flag = 1 + params.two_stage_flag = 0 + params.macro_halo_x = 2000 + params.macro_halo_y = 2000 + params.routability_opt_flag = 0 + params.get_congestion_map = 0 + params.egr_padding_flag = 0 + elif mode is DreamplaceRunMode.LEGALIZATION: params.global_place_flag = 0 params.legalize_flag = 1 params.enable_fillers = 0 @@ -67,22 +86,28 @@ def _build_params(self, params_cls, *, legalize_only: bool): return params - def _log_path(self, *, legalize_only: bool) -> str: - log_name = "dreamplace_legalization.log" if legalize_only else "dreamplace_placement.log" + def _log_path(self, *, mode: DreamplaceRunMode) -> str: + log_name = { + DreamplaceRunMode.PLACEMENT: "dreamplace_placement.log", + DreamplaceRunMode.MACRO_PLACEMENT: "dreamplace_macro_placement.log", + DreamplaceRunMode.LEGALIZATION: "dreamplace_legalization.log", + }[mode] return os.path.join(self.result_dir, log_name) - def _file_handler_path(self, *, legalize_only: bool) -> str: - if legalize_only and self.step.name != StepEnum.LEGALIZATION.value: - return self._log_path(legalize_only=True) - return str(self.step.log.file or self._log_path(legalize_only=legalize_only)) + def _file_handler_path(self, *, mode: DreamplaceRunMode) -> str: + if mode is DreamplaceRunMode.MACRO_PLACEMENT: + return self._log_path(mode=mode) + if mode is DreamplaceRunMode.LEGALIZATION and self.step.name != StepEnum.LEGALIZATION.value: + return self._log_path(mode=mode) + return str(self.step.log.file or self._log_path(mode=mode)) @contextmanager - def _configure_root_logging(self, *, legalize_only: bool): + def _configure_root_logging(self, *, mode: DreamplaceRunMode): root_logger = logging.getLogger() original_handlers = root_logger.handlers[:] original_level = root_logger.level - log_file = self._file_handler_path(legalize_only=legalize_only) + log_file = self._file_handler_path(mode=mode) os.makedirs(os.path.dirname(log_file) or ".", exist_ok=True) formatter = logging.Formatter("[%(levelname)-7s] %(message)s") @@ -107,31 +132,42 @@ def _configure_root_logging(self, *, legalize_only: bool): if handler not in root_logger.handlers: root_logger.addHandler(handler) - def _run(self, *, legalize_only: bool) -> bool: + def _run(self, *, mode: DreamplaceRunMode) -> bool: from dreamplace.Params import Params from dreamplace.Placer import PlacementEngine - with self._configure_root_logging(legalize_only=legalize_only): - params = self._build_params(Params, legalize_only=legalize_only) + with self._configure_root_logging(mode=mode): + params = self._build_params(Params, mode=mode) engine = PlacementEngine(params) engine.setup_rawdb(ecc_module=self.ecc_module) ppa = engine.run() - if ppa.get("hpwl") == float("inf"): - LOGGER = logging.getLogger(__name__) - LOGGER.error("dreamplace failed for %s", self.step.name) + skipped_empty_macro_placement = ( + mode is DreamplaceRunMode.MACRO_PLACEMENT + and ppa.get("executed") is False + and ppa.get("candidate_count") == 0 + and ppa.get("reason") == "no_unplaced_hard_macros" + ) + if skipped_empty_macro_placement: + return True + + if ppa.get("hpwl", float("inf")) == float("inf"): + logging.getLogger(__name__).error("dreamplace failed for %s", self.step.name) return False return True def run_placement(self) -> bool: - return self._run(legalize_only=False) + return self._run(mode=DreamplaceRunMode.PLACEMENT) + + def run_macro_placement(self) -> bool: + return self._run(mode=DreamplaceRunMode.MACRO_PLACEMENT) def run_legalization(self) -> bool: if self.step.name not in _LEGALIZE_OWNERS: return False - return self._run(legalize_only=True) + return self._run(mode=DreamplaceRunMode.LEGALIZATION) -__all__ = ["DreamplaceModule"] +__all__ = ["DreamplaceModule", "DreamplaceRunMode"] diff --git a/chipcompiler/tools/ecc_dreamplace/runner.py b/chipcompiler/tools/ecc_dreamplace/runner.py index 016ed3a1c..050370177 100644 --- a/chipcompiler/tools/ecc_dreamplace/runner.py +++ b/chipcompiler/tools/ecc_dreamplace/runner.py @@ -33,6 +33,8 @@ def run_step( state = False match step.name: + case StepEnum.MACRO_PLACEMENT.value: + state = run_macro_placement(workspace=workspace, step=step, ecc_module=ecc_module) case StepEnum.PLACEMENT.value: state = run_placement(workspace=workspace, step=step, ecc_module=ecc_module) case StepEnum.LEGALIZATION.value: @@ -41,6 +43,50 @@ def run_step( return state +def run_macro_placement( + workspace: Workspace, step: EccStep, ecc_module: ECCToolsModule | None = None +) -> bool: + """Run macro-only placement between the two floorplan phases.""" + reslut = False + sub_flow = EccSubFlow(workspace=workspace, workspace_step=step) + + ecc_module = ecc_runner.get_eda_instance(workspace=workspace, step=step, ecc_module=ecc_module) + + if ecc_module is not None: + sub_flow.update_step(step_name=EccSubFlowEnum.load_data.value, state=StateEnum.Success) + + dreamplace_module = DreamplaceModule( + workspace=workspace, + step=step, + ecc_module=ecc_module, + input_def=step.input.def_, + input_verilog=step.input.verilog, + output_def=step.output.def_, + output_verilog=step.output.verilog, + ) + reslut = dreamplace_module.run_macro_placement() + if not reslut: + sub_flow.update_step( + step_name=EccSubFlowEnum.macro_place.value, state=StateEnum.Imcomplete + ) + return False + + reslut = ecc_module.tcl_save(workspace.config.get("macro_location", "")) + if not reslut: + sub_flow.update_step( + step_name=EccSubFlowEnum.macro_place.value, state=StateEnum.Imcomplete + ) + return False + + sub_flow.update_step(step_name=EccSubFlowEnum.macro_place.value, state=StateEnum.Success) + reslut = ecc_runner.save_data( + workspace=workspace, step=step, ecc_module=ecc_module, feature_step=False + ) + sub_flow.update_step(step_name=EccSubFlowEnum.save_data.value, state=StateEnum.Success) + + return reslut + + def run_placement( workspace: Workspace, step: EccStep, ecc_module: ECCToolsModule | None = None ) -> bool: diff --git a/docs/index.md b/docs/index.md index 72997594c..efe18b716 100644 --- a/docs/index.md +++ b/docs/index.md @@ -14,6 +14,7 @@ The `ecc` command-line tool ships bilingual guides (`.en.md` / `.cn.md`): - 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](../chipcompiler/docs/ecc-config-ref.en.md)** / **[中文配置参考](../chipcompiler/docs/ecc-config-ref.cn.md)** - `ecc.toml`, workspace files, and the parameter system +- **[Staged Floorplan Flow](../chipcompiler/docs/floorplan-flow.en.md)** / **[分阶段 Floorplan 流程](../chipcompiler/docs/floorplan-flow.cn.md)** - Pre-floorplan, macro placement, post-floorplan, and the macro-location Tcl handoff - **[RPC Guide](rpc-guide.md)** - Private JSON-RPC runtime sidecar protocol (`ecc rpc serve`) ## Core Documentation diff --git a/test/cli/commands/conftest.py b/test/cli/commands/conftest.py index 10cd97022..b16fce1c8 100644 --- a/test/cli/commands/conftest.py +++ b/test/cli/commands/conftest.py @@ -157,7 +157,7 @@ def _build(project_dir): @pytest.fixture def create_legacy_workspace(): - """Factory: a real runs/ workspace with a Synthesis..Floorplan + """Factory: a real runs/ workspace with a Synthesis..postFloorplan flow ledger cut from the canonical chain. *states* holds (first step state, last step state); steps between them @@ -189,7 +189,7 @@ def _create(project_dir, pdk_root, run_id, states): for step, tool, _state in build_rtl2gds_flow() ] tools = dict(chain) - names = flow_steps_in_range("Synthesis", "Floorplan") + names = flow_steps_in_range("Synthesis", "postFloorplan") step_states = [states[0]] * (len(names) - 1) + [states[1]] steps = [ { diff --git a/test/cli/commands/test_flow_continuation.py b/test/cli/commands/test_flow_continuation.py index ea451480d..2a8b7b57a 100644 --- a/test/cli/commands/test_flow_continuation.py +++ b/test/cli/commands/test_flow_continuation.py @@ -3,6 +3,7 @@ from pathlib import Path from chipcompiler.cli import main as cli_main +from chipcompiler.rtl2gds import build_rtl2gds_flow def _write_existing_workspace(run_dir, step_names, states=None, preset="rtl2gds", pdk_root=None): @@ -38,21 +39,8 @@ def _write_existing_workspace(run_dir, step_names, states=None, preset="rtl2gds" RTL2GDS_NAMES = [ - "Synthesis", - "lec", - "Floorplan", - "place", - "CTS", - "legalization", - "Timing optimization", - "route", - "filler", - "RCX", - "sta", - "lvs", - "postRouteLec", - "drc", - "Harden", + step.value if hasattr(step, "value") else str(step) + for step, _tool, _state in build_rtl2gds_flow() ] diff --git a/test/cli/commands/test_migrate.py b/test/cli/commands/test_migrate.py index 2bd767a07..32874ef8b 100644 --- a/test/cli/commands/test_migrate.py +++ b/test/cli/commands/test_migrate.py @@ -61,7 +61,7 @@ def test_full_migration_moves_rebases_and_registers( assert entry["workspace_id"] == "exp1" assert entry["workspace_path"] == target assert entry["start_step"] == "Synth" - assert entry["end_step"] == "Floor" + assert entry["end_step"] == "PostFloorplan" assert entry["status"] == "success" assert manifest["design_name"] == "gcd" assert manifest["qor_baseline"]["workspace_id"] == "exp1" @@ -135,7 +135,7 @@ def test_resume_appends_missing_entries( "workspace_path": os.path.join(project_dir, "exp1"), "status": "success", "start_step": "Synth", - "end_step": "Floor", + "end_step": "PostFloorplan", } ], } @@ -679,7 +679,7 @@ def test_resume_append_disclosed_in_preview( "workspace_path": os.path.join(project_dir, "exp1"), "status": "success", "start_step": "Synth", - "end_step": "Floor", + "end_step": "PostFloorplan", } ], } diff --git a/test/cli/commands/test_report_step.py b/test/cli/commands/test_report_step.py index 983be0ccb..00373ba9d 100644 --- a/test/cli/commands/test_report_step.py +++ b/test/cli/commands/test_report_step.py @@ -273,7 +273,9 @@ def test_overview_includes_every_rtl2gds_step(self, tmp_path, capsys, plain_reco assert [record["step"] for record in records[1:]] == [ "synthesis", "lec", - "floorplan", + "pre_floorplan", + "macro_placement", + "post_floorplan", "placement", "cts", "legalization", diff --git a/test/cli/commands/test_status.py b/test/cli/commands/test_status.py index 99f458619..372b0ce55 100644 --- a/test/cli/commands/test_status.py +++ b/test/cli/commands/test_status.py @@ -28,7 +28,9 @@ def test_status_normalizes_every_rtl2gds_step( assert [record["step"] for record in records if "step" in record] == [ "synthesis", "lec", - "floorplan", + "pre_floorplan", + "macro_placement", + "post_floorplan", "placement", "cts", "legalization", diff --git a/test/data/test_config_coverage.py b/test/data/test_config_coverage.py index 9db456026..04107e086 100644 --- a/test/data/test_config_coverage.py +++ b/test/data/test_config_coverage.py @@ -68,6 +68,7 @@ ("base_design_name",), ("def_input",), ("lef_input",), + ("macro_only",), ("result_dir",), ("verilog_input",), }, diff --git a/test/data/test_legacy_migration.py b/test/data/test_legacy_migration.py index 6b8fdf080..78c79d146 100644 --- a/test/data/test_legacy_migration.py +++ b/test/data/test_legacy_migration.py @@ -236,7 +236,7 @@ def test_migration_seeds_flow_section_from_persisted_flow( steps = { "steps": [ {"name": "Synthesis", "tool": "yosys", "state": "Success"}, - {"name": "Floorplan", "tool": "ecc", "state": "Success"}, + {"name": "postFloorplan", "tool": "ecc", "state": "Success"}, ] } (workspace_dir / "home" / "flow.json").write_text(json.dumps(steps)) @@ -244,7 +244,7 @@ def test_migration_seeds_flow_section_from_persisted_flow( loaded = load_workspace(str(workspace_dir)) assert loaded is not None - assert loaded.parameters.data["_flow"] == {"start": "Synthesis", "end": "Floorplan"} + assert loaded.parameters.data["_flow"] == {"start": "Synthesis", "end": "postFloorplan"} def _fail_first_decode(monkeypatch): diff --git a/test/data/test_workspace.py b/test/data/test_workspace.py index 5f326030c..4a0d9d335 100644 --- a/test/data/test_workspace.py +++ b/test/data/test_workspace.py @@ -27,6 +27,7 @@ StepEnum.CTS.value: "cts_ecc.json", StepEnum.DRC.value: "drc_ecc.json", StepEnum.FLOORPLAN.value: "floorplan_ecc.json", + "macro_location": "macro_localtion.tcl", StepEnum.ROUTING.value: "route_ecc.json", StepEnum.FILLER.value: "filler_ecc.json", StepEnum.RCX.value: "rcx_ecc.json", @@ -130,6 +131,7 @@ def test_create_workspace_returns_path_fields_and_persists_string_paths( assert isinstance(workspace.parameters.path, Path) assert isinstance(workspace.home.path, Path) assert all(isinstance(path, Path) for path in workspace.config.values()) + assert workspace.config["macro_location"].is_file() home_data = json.loads((workspace_dir / "home" / "home.json").read_text()) assert home_data["flow"] == str(workspace.flow.path) @@ -265,7 +267,7 @@ def test_load_workspace_keeps_golden_prefixed_primary_netlist( pdk="ics55", parameters=deepcopy(default_ics55_parameters), pdk_root=pdk_root, - flow_config={"start_step": "Synthesis", "end_step": "Floorplan"}, + flow_config={"start_step": "Synthesis", "end_step": "postFloorplan"}, ) loaded = load_workspace(str(workspace_dir)) @@ -326,7 +328,9 @@ def test_create_workspace_non_contiguous_flow_seeds_both_stores_contiguous( assert [step["name"] for step in flow_data["steps"]] == [ "Synthesis", "lec", - "Floorplan", + "preFloorplan", + "macroPlacement", + "postFloorplan", "place", "CTS", ] @@ -650,6 +654,19 @@ def test_workspace_config_path_handles_known_and_unknown_keys(tmp_path): def test_step_config_keys_return_workspace_config_keys(): assert data_api.step_config_keys("CTS", "ecc") == ("db", StepEnum.CTS.value) + assert data_api.step_config_keys(StepEnum.PRE_FLOORPLAN, "ecc") == ( + "db", + StepEnum.FLOORPLAN.value, + ) + assert data_api.step_config_keys(StepEnum.MACRO_PLACEMENT, "dreamplace") == ( + "dreamplace", + "macro_location", + ) + assert data_api.step_config_keys(StepEnum.POST_FLOORPLAN, "ecc") == ( + "db", + StepEnum.FLOORPLAN.value, + "macro_location", + ) assert data_api.step_config_keys("place", "ecc") == ("db",) assert data_api.step_config_keys(StepEnum.PLACEMENT, "ecc") == ("db",) assert data_api.step_config_keys("legalization", "ecc") == ("db",) @@ -672,7 +689,7 @@ def test_step_config_keys_return_workspace_config_keys(): def test_step_config_keys_accept_exact_internal_step_names_only(): cases = [ - (StepEnum.FLOORPLAN.value, StepEnum.FLOORPLAN.value), + (StepEnum.POST_FLOORPLAN.value, StepEnum.FLOORPLAN.value), (StepEnum.ROUTING.value, StepEnum.ROUTING.value), (StepEnum.RCX.value, StepEnum.RCX.value), ("sta", StepEnum.STA.value), diff --git a/test/engine/test_reconcile.py b/test/engine/test_reconcile.py index 51a88a2ea..3a04ce7eb 100644 --- a/test/engine/test_reconcile.py +++ b/test/engine/test_reconcile.py @@ -152,75 +152,7 @@ def test_equal_with_legacy_warned_lec_resumes(self, tmp_path): assert result.outcome == "resume" - # The pre-reorder chain: DRC/LVS before filler, RCX/STA/Harden as - # preset-only suffixes. - LEGACY_RTL2GDS_ORDER = [ - ("Synthesis", "yosys"), - ("lec", "yosys_lec"), - ("Floorplan", "ecc"), - ("place", "dreamplace"), - ("CTS", "ecc"), - ("legalization", "dreamplace"), - ("Timing optimization", "sizer"), - ("route", "ecc"), - ("drc", "ecc"), - ("lvs", "ecc"), - ("filler", "ecc"), - ("postRouteLec", "yosys_lec"), - ] - - def test_legacy_prereorder_ledger_migrates_instead_of_mismatch(self, tmp_path): - workspace_dir = _write_workspace( - tmp_path, - self.LEGACY_RTL2GDS_ORDER, - flow_section={"preset": "rtl2gds"}, - ) - - result = reconcile_workspace(workspace_dir, {"preset": "rtl2gds"}) - - assert result.outcome == "resume" - steps = _flow_steps(workspace_dir) - assert [(s["name"], s["tool"]) for s in steps] == RTL2GDS_STEPS - route_index = next( - index for index, (name, _tool) in enumerate(RTL2GDS_STEPS) if name == "route" - ) - # The identical Synthesis..route prefix keeps its Success states; - # post-route steps ran with pre-reorder inputs and restart. - assert all(s["state"] == "Success" for s in steps[: route_index + 1]) - assert [s["state"] for s in steps[route_index + 1 :]] == ["Unstart"] * ( - len(steps) - route_index - 1 - ) - assert set(result.appended) == {"RCX", "sta", "Harden"} - - def test_legacy_prereorder_rcx_preset_migrates_to_legacy_alias_range(self, tmp_path): - workspace_dir = _write_workspace( - tmp_path, - self.LEGACY_RTL2GDS_ORDER + [("RCX", "ecc"), ("sta", "ecc")], - flow_section={"preset": "rcx"}, - ) - - result = reconcile_workspace(workspace_dir, {"preset": "rcx"}) - - assert result.outcome == "resume" - steps = _flow_steps(workspace_dir) - assert [(s["name"], s["tool"]) for s in steps] == [ - ("Synthesis", "yosys"), - ("lec", "yosys_lec"), - ("Floorplan", "ecc"), - ("place", "dreamplace"), - ("CTS", "ecc"), - ("legalization", "dreamplace"), - ("Timing optimization", "sizer"), - ("route", "ecc"), - ("filler", "ecc"), - ("RCX", "ecc"), - ("sta", "ecc"), - ] - assert _flow_section(workspace_dir) == {"preset": "rcx"} - def test_ledger_starting_off_synthesis_is_still_a_mismatch(self, tmp_path): - # Legacy presets always began at Synthesis: a ledger starting - # elsewhere is a foreign shape, not a migratable legacy one. workspace_dir = _write_workspace( tmp_path, [("place", "dreamplace")], flow_section={"preset": "rtl2gds"} ) diff --git a/test/formal/test_param_propagation.py b/test/formal/test_param_propagation.py index 47d8885ed..6a3efd5f3 100644 --- a/test/formal/test_param_propagation.py +++ b/test/formal/test_param_propagation.py @@ -21,7 +21,7 @@ WorkspaceStep, get_parameters, ) -from chipcompiler.tools.ecc_dreamplace.module import DreamplaceModule +from chipcompiler.tools.ecc_dreamplace.module import DreamplaceModule, DreamplaceRunMode from chipcompiler.utility import json_write # Every direct workspace parameter key accessed by builder/helper code, @@ -203,7 +203,7 @@ def test_routability_runtime_flags_are_config_driven(tmp_path) -> None: output_verilog=Path("output.v"), ) - params = module._build_params(FakeDreamplaceParams, legalize_only=False) + params = module._build_params(FakeDreamplaceParams, mode=DreamplaceRunMode.PLACEMENT) assert params.routability_opt_flag == 1 assert params.get_congestion_map == 1 diff --git a/test/rtl2gds/test_builder.py b/test/rtl2gds/test_builder.py index bb813d9fe..fc245d7b1 100644 --- a/test/rtl2gds/test_builder.py +++ b/test/rtl2gds/test_builder.py @@ -57,7 +57,9 @@ def test_build_rtl2gds_flow_is_the_complete_flow(): assert flow == [ (StepEnum.SYNTHESIS, "yosys", StateEnum.Unstart), (StepEnum.LEC, "yosys_lec", StateEnum.Unstart), - (StepEnum.FLOORPLAN, "ecc", StateEnum.Unstart), + (StepEnum.PRE_FLOORPLAN, "ecc", StateEnum.Unstart), + (StepEnum.MACRO_PLACEMENT, "dreamplace", StateEnum.Unstart), + (StepEnum.POST_FLOORPLAN, "ecc", StateEnum.Unstart), (StepEnum.PLACEMENT, "dreamplace", StateEnum.Unstart), (StepEnum.CTS, "ecc", StateEnum.Unstart), (StepEnum.LEGALIZATION, "dreamplace", StateEnum.Unstart), @@ -92,3 +94,13 @@ def test_build_flow_range_normalizes_aliases_and_rejects_reverse_ranges(): with pytest.raises(ValueError, match="reversed"): builder_module.build_flow_range("route", "CTS") + + +def test_build_flow_range_exposes_split_floorplan_steps(): + flow = builder_module.build_flow_range("preFloorplan", "postFloorplan") + + assert [(step, tool) for step, tool, _state in flow] == [ + (StepEnum.PRE_FLOORPLAN, "ecc"), + (StepEnum.MACRO_PLACEMENT, "dreamplace"), + (StepEnum.POST_FLOORPLAN, "ecc"), + ] diff --git a/test/runtime/test_layout_edit.py b/test/runtime/test_layout_edit.py index 4e43d54d6..20d3d21d7 100644 --- a/test/runtime/test_layout_edit.py +++ b/test/runtime/test_layout_edit.py @@ -157,24 +157,24 @@ def get_workspace_step(self, name): def _make_layout_workspace(tmp_path, *, with_db=False, with_editor_workspace=False): workspace_dir = tmp_path / "workspace" - output_dir = workspace_dir / "Floorplan_ecc" / "output" + output_dir = workspace_dir / "postFloorplan_ecc" / "output" output_dir.mkdir(parents=True) - output_def = output_dir / "gcd_Floorplan.def.gz" + output_def = output_dir / "gcd_postFloorplan.def.gz" output_def.write_text("old def", encoding="utf-8") - output_db = output_dir / "gcd_Floorplan_db" + output_db = output_dir / "gcd_postFloorplan_db" if with_db: output_db.mkdir() (output_db / "metadata.idb").write_text("old db", encoding="utf-8") step = SimpleNamespace( - name="Floorplan", + name="postFloorplan", input={"def": None, "verilog": None, "db": None}, output={ "def": output_def, "db": output_db, - "gds": output_dir / "gcd_Floorplan.gds", + "gds": output_dir / "gcd_postFloorplan.gds", "geometry": output_dir / "geometry", "geometry_manifest": output_dir / "geometry" / "geometry.manifest", - "verilog": output_dir / "gcd_Floorplan.v", + "verilog": output_dir / "gcd_postFloorplan.v", }, ) workspace = SimpleNamespace(directory=workspace_dir) @@ -187,7 +187,7 @@ def _make_layout_workspace(tmp_path, *, with_db=False, with_editor_workspace=Fal flow_path = workspace_dir / "flow.json" flow_data = { "steps": [ - {"name": "Floorplan", "state": "Success", "runtime": "1s"}, + {"name": "postFloorplan", "state": "Success", "runtime": "1s"}, {"name": "place", "state": "Success", "runtime": "2s"}, {"name": "route", "state": "Success", "runtime": "3s"}, ] @@ -225,7 +225,7 @@ def build_flow(_workspace): def _begin(api, workspace_id, **kwargs): return api.layout_edit_begin( - LayoutEditBeginRequest(workspace_id=workspace_id, step="Floorplan", **kwargs) + LayoutEditBeginRequest(workspace_id=workspace_id, step="postFloorplan", **kwargs) ) diff --git a/test/test_engine_flow.py b/test/test_engine_flow.py index d8ed989cf..ad7b9101e 100644 --- a/test/test_engine_flow.py +++ b/test/test_engine_flow.py @@ -48,10 +48,12 @@ def test_engine_flow_default_steps_include_synthesis_lec(tmp_path): engine_flow = EngineFlow(workspace) engine_flow.build_default_steps() - assert [(step["name"], step["tool"]) for step in workspace.flow.data["steps"][:3]] == [ + assert [(step["name"], step["tool"]) for step in workspace.flow.data["steps"][:5]] == [ (StepEnum.SYNTHESIS.value, "yosys"), (StepEnum.LEC.value, "yosys_lec"), - (StepEnum.FLOORPLAN.value, "ecc"), + (StepEnum.PRE_FLOORPLAN.value, "ecc"), + (StepEnum.MACRO_PLACEMENT.value, "dreamplace"), + (StepEnum.POST_FLOORPLAN.value, "ecc"), ] diff --git a/test/test_engine_rerun.py b/test/test_engine_rerun.py index b7248232b..65a9fbff2 100644 --- a/test/test_engine_rerun.py +++ b/test/test_engine_rerun.py @@ -89,6 +89,18 @@ def test_resume_all_success_selects_nothing(self, tmp_path): assert rerun.selected_step_names(flow) == [] + def test_resume_skips_completed_pre_floorplan(self, tmp_path): + flow = _make_run_flow( + tmp_path, + [ + ("preFloorplan", "Success"), + ("macroPlacement", "Unstart"), + ("postFloorplan", "Unstart"), + ], + ) + + assert rerun.selected_step_names(flow) == ["macroPlacement", "postFloorplan"] + def test_resume_reexecutes_legacy_warning_steps(self, tmp_path): # The removed terminal Warning state (pre-rework synthesis LEC) is not # a finished state: a plain resume re-executes it and its suffix. diff --git a/test/test_qor_report.py b/test/test_qor_report.py index bd63e7f44..c14c3f1cc 100644 --- a/test/test_qor_report.py +++ b/test/test_qor_report.py @@ -338,7 +338,7 @@ def test_flow_steps_follow_the_canonical_chain_order(self): assert FLOW_STEPS == ( "Synth", - "Floor", + "PostFloorplan", "Place", "CTS", "Legal", diff --git a/test/test_signoff_report.py b/test/test_signoff_report.py index dce5776a0..b432c605b 100644 --- a/test/test_signoff_report.py +++ b/test/test_signoff_report.py @@ -34,6 +34,7 @@ def test_format_duration(self): def test_canonicalize_stage_name(self): assert canonicalize_stage_name("Synthesis_yosys") == "Synth" + assert canonicalize_stage_name("postFloorplan_ecc") == "PostFloorplan" assert canonicalize_stage_name("legalization_dreamplace") == "Legal" assert canonicalize_stage_name("custom_step") == "custom_step" @@ -82,15 +83,17 @@ def test_query_schema3_metrics_array(self): def test_query_dotted_path_and_normalized_key(self): store = StepMetricStore() - store.add("Floorplan_ecc", {"Design Layout": {"die_area": 1234.5}}) - value, _, _ = store.query("Physical", "Die Area", ["Floor"], ["Design Layout.die_area"]) + store.add("postFloorplan_ecc", {"Design Layout": {"die_area": 1234.5}}) + value, _, _ = store.query( + "Physical", "Die Area", ["PostFloorplan"], ["Design Layout.die_area"] + ) assert value == 1234.5 store.add("route_ecc", {"RouteDrTotalViaCount": 7}) assert store.query("Routing", "Via", ["Route"], ["route_dr_total_via_count"])[0] == 7.0 def test_query_miss_returns_none(self): store = StepMetricStore() - assert store.query("Physical", "Die Area", ["Floor"], ["die_area"])[0] is None + assert store.query("Physical", "Die Area", ["PostFloorplan"], ["die_area"])[0] is None class TestExtractDesignReportData: diff --git a/test/tools/ecc/test_module.py b/test/tools/ecc/test_module.py index 7a3b6eb15..05c363924 100644 --- a/test/tools/ecc/test_module.py +++ b/test/tools/ecc/test_module.py @@ -5,6 +5,8 @@ from pathlib import Path from types import SimpleNamespace +import pytest + import chipcompiler.utility as chipcompiler_utility from chipcompiler.data import OriginDesign, StepEnum, Workspace from chipcompiler.tools.ecc import metrics as ecc_metrics @@ -156,6 +158,15 @@ def test_init_rcx_omits_explicit_empty_pdk_for_backward_compatibility(): assert module.ecc.calls == [{"config": "/tmp/rcx_ecc.json"}] +def test_run_simple_fp_calls_native_api(): + module = ECCToolsModule.__new__(ECCToolsModule) + module.ecc = FakeEcc() + + assert module.run_simple_fp() is True + + assert module.ecc.calls == [("run_simple_fp", (), {})] + + def test_view_json_save_passes_output_options(): module = ECCToolsModule.__new__(ECCToolsModule) module.ecc = FakeEcc() @@ -365,7 +376,7 @@ def test_ecc_runtime_wrappers_stringify_path_arguments(tmp_path): assert module.read_lvs_verilog(Path("/ws/input_lvs.v"), "gcd") is True module.def_save(Path("/ws/output/gcd.def.gz")) module.gds_save(Path("/ws/output/gcd.gds.gz"), is_harden=True) - module.tcl_save(Path("/ws/script/out.tcl")) + assert module.tcl_save(Path("/ws/script/out.tcl")) is True module.verilog_save(Path("/ws/output/gcd.v.gz")) module.json_save(Path("/ws/output/gcd.json")) module.save_data(Path("/ws/output/db")) @@ -2422,6 +2433,50 @@ def test_ecc_subflow_writes_path_payload_as_json_strings(tmp_path): assert data["path"] == str(step.subflow.path) +@pytest.mark.parametrize( + ("step_name", "expected"), + [ + ( + StepEnum.PRE_FLOORPLAN.value, + ["load data", "init floorplan", "save data"], + ), + ( + StepEnum.MACRO_PLACEMENT.value, + ["load data", "macro placement", "save data"], + ), + ( + StepEnum.POST_FLOORPLAN.value, + [ + "load data", + "create tracks", + "place io pins", + "tap cell", + "PDN", + "set clock net", + "save data", + "analysis", + ], + ), + ], +) +def test_split_floorplan_subflows_are_independent(tmp_path, step_name, expected): + workspace = Workspace( + directory=tmp_path, + design=OriginDesign(name="gcd", top_module="gcd"), + ) + step = build_step( + workspace=workspace, + step_name=step_name, + input_def=tmp_path / "input.def", + input_verilog=tmp_path / "input.v", + ) + build_step_space(step) + + EccSubFlow(workspace, step) + + assert [item["name"] for item in step.subflow.steps] == expected + + def test_ecc_step_info_stringifies_path_payloads(tmp_path, monkeypatch): workspace = Workspace( directory=tmp_path, diff --git a/test/tools/ecc/test_pyplacedb_macro_writeback.py b/test/tools/ecc/test_pyplacedb_macro_writeback.py new file mode 100644 index 000000000..865f0ad2f --- /dev/null +++ b/test/tools/ecc/test_pyplacedb_macro_writeback.py @@ -0,0 +1,452 @@ +import json +from pathlib import Path +from textwrap import dedent + +import numpy as np +import pytest + + +def _new_place_db(ecc_py): + return ecc_py.pydb(ecc_py.get_dmInst(), 2, 2, False, False) # noqa: FBT003 + + +def _macro(name: str, cell_class: str, size: tuple[int, int], pin_name: str) -> str: + width, height = size + return dedent( + f""" + MACRO {name} + CLASS {cell_class} ; + ORIGIN 0 0 ; + SIZE {width} BY {height} ; + SYMMETRY X Y ; + SITE core ; + PIN {pin_name} + DIRECTION INPUT ; + PORT + LAYER M1 ; + RECT 0 0 1 1 ; + END + END {pin_name} + END {name} + """ + ) + + +@pytest.fixture +def mixed_macro_place_db(tmp_path): + ecc_py = pytest.importorskip("ecc_tools_bin.ecc_py") + + tech_lef = tmp_path / "tech.lef" + tech_lef.write_text( + dedent( + """ + VERSION 5.8 ; + BUSBITCHARS "[]" ; + DIVIDERCHAR "/" ; + UNITS + DATABASE MICRONS 1000 ; + END UNITS + MANUFACTURINGGRID 0.001 ; + LAYER M1 + TYPE ROUTING ; + DIRECTION HORIZONTAL ; + PITCH 1 ; + WIDTH 1 ; + SPACING 1 ; + END M1 + LAYER M2 + TYPE ROUTING ; + DIRECTION VERTICAL ; + PITCH 1 ; + WIDTH 1 ; + SPACING 1 ; + END M2 + SITE core + CLASS CORE ; + SIZE 1 BY 1 ; + SYMMETRY X Y ; + END core + END LIBRARY + """ + ), + encoding="utf-8", + ) + + cells_lef = tmp_path / "cells.lef" + cells_lef.write_text( + "VERSION 5.8 ;\n" + 'BUSBITCHARS "[]" ;\n' + 'DIVIDERCHAR "/" ;\n' + + _macro("HARD_BLOCK", "BLOCK", (10, 10), "P") + + _macro("LARGE_CORE", "CORE", (100, 10), "A") + + _macro("CORE", "CORE", (1, 1), "A") + + "END LIBRARY\n", + encoding="utf-8", + ) + + rows = "".join( + f"ROW ROW{row} core 0 {row * 1000} N DO 300 BY 1 STEP 1000 0 ;\n" for row in range(20) + ) + design_def = tmp_path / "design.def" + design_def.write_text( + dedent( + """ + VERSION 5.8 ; + DIVIDERCHAR "/" ; + BUSBITCHARS "[]" ; + DESIGN macro_status_test ; + UNITS DISTANCE MICRONS 1000 ; + DIEAREA ( 0 0 ) ( 300000 20000 ) ; + """ + ) + + rows + + dedent( + """ + COMPONENTS 7 ; + - macro_none HARD_BLOCK ; + - macro_unplaced HARD_BLOCK + UNPLACED ; + - macro_placed HARD_BLOCK + PLACED ( 20000 0 ) N ; + - macro_fixed HARD_BLOCK + FIXED ( 40000 0 ) N ; + - macro_cover HARD_BLOCK + COVER ( 60000 0 ) N ; + - large_core LARGE_CORE + PLACED ( 80000 0 ) N ; + - core_unplaced CORE + UNPLACED ; + END COMPONENTS + PINS 1 ; + - input + NET signal + DIRECTION INPUT + USE SIGNAL + + LAYER M1 ( 0 0 ) ( 1 1 ) + FIXED ( 0 0 ) N ; + END PINS + NETS 1 ; + - signal ( PIN input ) ( macro_none P ) ( macro_unplaced P ) ( macro_placed P ) + ( macro_fixed P ) ( macro_cover P ) ( large_core A ) ( core_unplaced A ) ; + END NETS + BLOCKAGES 1 ; + - PLACEMENT RECT ( 250000 10000 ) ( 260000 20000 ) ; + END BLOCKAGES + END DESIGN + """ + ), + encoding="utf-8", + ) + + ecc_py.reset_data() + assert ecc_py.tech_lef_init(str(tech_lef)) + assert ecc_py.lef_init([str(cells_lef)]) + assert ecc_py.def_init(str(design_def)) + try: + yield ecc_py, _new_place_db(ecc_py), tmp_path + finally: + ecc_py.reset_data() + + +def test_pyplacedb_freezes_only_none_and_unplaced_hard_macros_as_candidates( + mixed_macro_place_db, +): + _, place_db, _ = mixed_macro_place_db + names = list(place_db.node_names) + hard_macros = dict(zip(names, place_db.node_is_hard_macro, strict=True)) + candidates = dict(zip(names, place_db.macro_writeback_candidate, strict=True)) + + assert {name for name, is_macro in hard_macros.items() if is_macro} == { + "macro_none", + "macro_unplaced", + "macro_placed", + "macro_fixed", + "macro_cover", + } + assert {name for name, is_candidate in candidates.items() if is_candidate} == { + "macro_none", + "macro_unplaced", + } + assert hard_macros["input"] is False + assert candidates["input"] is False + blockage_names = [name for name in names if name.startswith("blockage")] + assert len(blockage_names) == 1 + assert hard_macros[blockage_names[0]] is False + assert candidates[blockage_names[0]] is False + + +def test_macro_writeback_commits_only_frozen_candidates(mixed_macro_place_db): + ecc_py, place_db, tmp_path = mixed_macro_place_db + names = list(place_db.node_names) + candidate_ids = np.flatnonzero(place_db.macro_writeback_candidate) + num_movable_nodes = place_db.num_nodes - place_db.num_terminals - place_db.num_terminal_NIs + before = { + name: (place_db.node_x[node_id], place_db.node_y[node_id], place_db.node_orient[node_id]) + for node_id, name in enumerate(names) + } + + node_x = np.asarray(place_db.node_x, dtype=np.float32)[:num_movable_nodes].copy() + node_y = np.asarray(place_db.node_y, dtype=np.float32)[:num_movable_nodes].copy() + node_x[:] = np.arange(num_movable_nodes, dtype=np.float32) * 1000 + 100000 + node_y[:] = 1000 + expected_candidate_locations = { + names[node_id]: (int(node_x[node_id]), int(node_y[node_id])) for node_id in candidate_ids + } + expected_candidate_orientations = { + name: "N_R0" if before[name][2] == "None" else before[name][2] + for name in expected_candidate_locations + } + + assert place_db.write_macro_placement_back(node_x, node_y) == len(candidate_ids) + + updated_db = _new_place_db(ecc_py) + updated_names = list(updated_db.node_names) + after = { + name: ( + updated_db.node_x[node_id], + updated_db.node_y[node_id], + updated_db.node_orient[node_id], + ) + for node_id, name in enumerate(updated_names) + } + for name, location in expected_candidate_locations.items(): + assert after[name][:2] == location + assert after[name][2] == expected_candidate_orientations[name] + assert { + name: state for name, state in after.items() if name not in expected_candidate_locations + } == {name: state for name, state in before.items() if name not in expected_candidate_locations} + assert updated_db.num_terminals == place_db.num_terminals + len(candidate_ids) + + output_def = tmp_path / "macro_writeback.def" + assert ecc_py.def_save(str(output_def)) + output_text = output_def.read_text(encoding="utf-8") + for name, (x, y) in expected_candidate_locations.items(): + component_start = output_text.index(f"- {name} ") + component_end = output_text.index(";", component_start) + component = output_text[component_start:component_end] + assert f"+ PLACED ( {x} {y} )" in component + + +def test_macro_writeback_validates_every_candidate_before_mutating(mixed_macro_place_db): + ecc_py, place_db, _ = mixed_macro_place_db + names = list(place_db.node_names) + macro_none_id = names.index("macro_none") + before = ( + place_db.node_x[macro_none_id], + place_db.node_y[macro_none_id], + place_db.node_orient[macro_none_id], + ) + num_movable_nodes = place_db.num_nodes - place_db.num_terminals - place_db.num_terminal_NIs + node_x = np.asarray(place_db.node_x, dtype=np.float32)[:num_movable_nodes].copy() + node_y = np.asarray(place_db.node_y, dtype=np.float32)[:num_movable_nodes].copy() + node_x[macro_none_id] = 125000 + node_y[macro_none_id] = 1000 + + assert ecc_py.delete_inst("macro_unplaced") + with pytest.raises(RuntimeError, match="no longer exists"): + place_db.write_macro_placement_back(node_x, node_y) + + updated_db = _new_place_db(ecc_py) + updated_names = list(updated_db.node_names) + updated_id = updated_names.index("macro_none") + assert ( + updated_db.node_x[updated_id], + updated_db.node_y[updated_id], + updated_db.node_orient[updated_id], + ) == before + assert updated_db.macro_writeback_candidate[updated_id] + + +def test_macro_writeback_rejects_float32_value_above_int32_max(mixed_macro_place_db): + ecc_py, place_db, _ = mixed_macro_place_db + names = list(place_db.node_names) + macro_none_id = names.index("macro_none") + before = (place_db.node_x[macro_none_id], place_db.node_y[macro_none_id]) + num_movable_nodes = place_db.num_nodes - place_db.num_terminals - place_db.num_terminal_NIs + node_x = np.asarray(place_db.node_x, dtype=np.float32)[:num_movable_nodes].copy() + node_y = np.asarray(place_db.node_y, dtype=np.float32)[:num_movable_nodes].copy() + node_x[macro_none_id] = np.float32(np.iinfo(np.int32).max) + + assert node_x[macro_none_id] == np.float32(2**31) + with pytest.raises(ValueError, match="int32-compatible"): + place_db.write_macro_placement_back(node_x, node_y) + + updated_db = _new_place_db(ecc_py) + updated_names = list(updated_db.node_names) + updated_id = updated_names.index("macro_none") + assert (updated_db.node_x[updated_id], updated_db.node_y[updated_id]) == before + + +def test_macro_writeback_rejects_replacement_with_same_instance_name(mixed_macro_place_db): + ecc_py, place_db, _ = mixed_macro_place_db + names = list(place_db.node_names) + macro_unplaced_id = names.index("macro_unplaced") + num_movable_nodes = place_db.num_nodes - place_db.num_terminals - place_db.num_terminal_NIs + node_x = np.asarray(place_db.node_x, dtype=np.float32)[:num_movable_nodes].copy() + node_y = np.asarray(place_db.node_y, dtype=np.float32)[:num_movable_nodes].copy() + node_x[macro_unplaced_id] = 125000 + node_y[macro_unplaced_id] = 1000 + + assert ecc_py.delete_inst("macro_unplaced") + assert ecc_py.create_inst("macro_unplaced", "HARD_BLOCK", 222000, 2000, "N") + with pytest.raises(RuntimeError, match="same instance"): + place_db.write_macro_placement_back(node_x, node_y) + + updated_db = _new_place_db(ecc_py) + updated_names = list(updated_db.node_names) + replacement_id = updated_names.index("macro_unplaced") + assert (updated_db.node_x[replacement_id], updated_db.node_y[replacement_id]) == (222000, 2000) + + +def test_macro_placement_engine_smoke_commits_candidates_as_fixed(mixed_macro_place_db): + from dreamplace.Params import Params + from dreamplace.Placer import PlacementEngine + + from chipcompiler.tools.ecc.module import ECCToolsModule + + ecc_py, place_db, tmp_path = mixed_macro_place_db + input_nodes = { + name: { + "x": place_db.node_x[node_id], + "y": place_db.node_y[node_id], + "size_x": place_db.node_size_x[node_id], + "size_y": place_db.node_size_y[node_id], + } + for node_id, name in enumerate(place_db.node_names) + if place_db.macro_writeback_candidate[node_id] + } + config_path = ( + Path(__file__).resolve().parents[3] + / "chipcompiler/tools/ecc_dreamplace/configs/dreamplace_ecc.json" + ) + params = Params() + params.fromJson(json.loads(config_path.read_text(encoding="utf-8"))) + params.macro_only = 1 + params.global_place_flag = 1 + params.macro_place_flag = 1 + params.legalize_flag = 1 + params.two_stage_flag = 0 + params.routability_opt_flag = 0 + params.get_congestion_map = 0 + params.egr_padding_flag = 0 + params.macro_halo_x = 1000 + params.macro_halo_y = 1000 + params.macro_pin_halo_x = -1 + params.macro_pin_halo_y = -1 + params.cell_padding_x = 0 + params.enable_fillers = 0 + params.auto_adjust_bins = 0 + params.num_bins_x = 4 + params.num_bins_y = 4 + params.num_threads = 1 + params.plot_flag = 0 + params.result_dir = str(tmp_path) + params.base_design_name = "macro_status_test" + params.global_place_stages[0]["iteration"] = 5 + params.stop_overflow = 1.0 + + engine = PlacementEngine(params) + engine.setup_rawdb(ecc_module=ECCToolsModule()) + halo_setup = {} + setup_placedb = engine.setup_placedb + + def capture_halo_setup(): + setup_placedb() + candidate_ids = np.flatnonzero(engine.placedb.macro_writeback_candidate) + halo_setup.update( + candidate_ids=candidate_ids, + halo_x=engine.params.macro_halo_x, + halo_y=engine.params.macro_halo_y, + node_x=engine.placedb.node_x[candidate_ids].copy(), + node_y=engine.placedb.node_y[candidate_ids].copy(), + node_size_x=engine.placedb.node_size_x[candidate_ids].copy(), + node_size_y=engine.placedb.node_size_y[candidate_ids].copy(), + ) + + engine.setup_placedb = capture_halo_setup + result = engine.run() + + assert result.get("executed") is not False, ( + result, + list(engine.placedb.pydb.macro_writeback_candidate), + list(engine.placedb.pydb.node_names), + ) + assert result["hpwl"] != float("inf") + assert halo_setup["halo_x"] > 0 + assert halo_setup["halo_y"] > 0 + scale_factor = engine.params.scale_factor + shift_x, shift_y = engine.params.shift_factor + candidate_names = [ + engine.placedb.node_names[node_id].decode() for node_id in halo_setup["candidate_ids"] + ] + for offset, name in enumerate(candidate_names): + input_node = input_nodes[name] + assert halo_setup["node_x"][offset] == pytest.approx( + (input_node["x"] - shift_x) * scale_factor - halo_setup["halo_x"] + ) + assert halo_setup["node_y"][offset] == pytest.approx( + (input_node["y"] - shift_y) * scale_factor - halo_setup["halo_y"] + ) + assert halo_setup["node_size_x"][offset] == pytest.approx( + input_node["size_x"] * scale_factor + 2 * halo_setup["halo_x"] + ) + assert halo_setup["node_size_y"][offset] == pytest.approx( + input_node["size_y"] * scale_factor + 2 * halo_setup["halo_y"] + ) + + assert engine.params.macro_halo_x == 0 + assert engine.params.macro_halo_y == 0 + np.testing.assert_allclose( + engine.placer.data_collections.node_size_x[halo_setup["candidate_ids"]].cpu(), + [input_nodes[name]["size_x"] * scale_factor for name in candidate_names], + ) + np.testing.assert_allclose( + engine.placer.data_collections.node_size_y[halo_setup["candidate_ids"]].cpu(), + [input_nodes[name]["size_y"] * scale_factor for name in candidate_names], + ) + updated_db = _new_place_db(ecc_py) + updated_locations = { + name: (updated_db.node_x[node_id], updated_db.node_y[node_id]) + for node_id, name in enumerate(updated_db.node_names) + } + for node_id, name in zip(halo_setup["candidate_ids"], candidate_names, strict=True): + assert updated_locations[name] == pytest.approx( + ( + engine.placedb.node_x[node_id] / scale_factor + shift_x, + engine.placedb.node_y[node_id] / scale_factor + shift_y, + ), + abs=1, + ) + updated_candidates = dict( + zip(updated_db.node_names, updated_db.macro_writeback_candidate, strict=True) + ) + assert updated_candidates["macro_none"] is False + assert updated_candidates["macro_unplaced"] is False + assert updated_db.num_terminals == place_db.num_terminals + 2 + + committed_locations = {name: updated_locations[name] for name in candidate_names} + normal_params = Params() + normal_params.fromJson(json.loads(config_path.read_text(encoding="utf-8"))) + normal_params.macro_only = 0 + normal_params.routability_opt_flag = 0 + normal_params.get_congestion_map = 0 + normal_params.egr_padding_flag = 0 + normal_params.macro_halo_x = 0 + normal_params.macro_halo_y = 0 + normal_params.cell_padding_x = 0 + normal_params.enable_fillers = 0 + normal_params.auto_adjust_bins = 0 + normal_params.num_bins_x = 4 + normal_params.num_bins_y = 4 + normal_params.num_threads = 1 + normal_params.result_dir = str(tmp_path / "normal-placement") + normal_params.base_design_name = "macro_status_test" + normal_params.global_place_stages[0]["iteration"] = 5 + normal_params.stop_overflow = 1.0 + + normal_engine = PlacementEngine(normal_params) + normal_engine.setup_rawdb(ecc_module=ECCToolsModule()) + normal_result = normal_engine.run() + + assert normal_result["hpwl"] != float("inf") + fixed_names = { + name.decode() + for name in normal_engine.placedb.node_names[normal_engine.placedb.fixed_slice] + } + assert set(candidate_names) <= fixed_names + after_normal_db = _new_place_db(ecc_py) + after_normal_locations = { + name: (after_normal_db.node_x[node_id], after_normal_db.node_y[node_id]) + for node_id, name in enumerate(after_normal_db.node_names) + } + assert {name: after_normal_locations[name] for name in candidate_names} == committed_locations diff --git a/test/tools/ecc/test_runner.py b/test/tools/ecc/test_runner.py index 4def6b45b..0518e01ef 100644 --- a/test/tools/ecc/test_runner.py +++ b/test/tools/ecc/test_runner.py @@ -114,7 +114,7 @@ def update_step(self, **kwargs): ) def test_run_analysis_switch(parameters, expected_calls, tmp_path, monkeypatch): workspace = Workspace(directory=tmp_path, parameters=Parameters(data=parameters)) - step = EccStep(name=StepEnum.FLOORPLAN.value) + step = EccStep(name=StepEnum.POST_FLOORPLAN.value) metrics = Mock() plotter = Mock() checklist = Mock() @@ -131,6 +131,92 @@ def test_run_analysis_switch(parameters, expected_calls, tmp_path, monkeypatch): assert checklist.return_value.check.call_count == expected_calls +def test_split_floorplan_runs_pre_and_post_phases_independently(monkeypatch, tmp_path): + calls = [] + saved_steps = [] + analyzed_steps = [] + + class FakeFloorplanModule: + def init_fp(self, config): + calls.append(("init_fp", config)) + + def run_simple_fp(self): + calls.append(("run_simple_fp",)) + + def run_fp(self): + calls.append(("run_fp",)) + + def destroy_fp(self): + calls.append(("destroy_fp",)) + + floorplan_config = tmp_path / "floorplan_ecc.json" + floorplan_config.write_text( + json.dumps( + { + "macro_placer": {"mode": "file", "file_path": "old_locations.txt"}, + "io_placer": {"mode": "file", "file_path": "io_pins.txt"}, + } + ) + ) + simple_floorplan_config = floorplan_config.with_stem("floorplan_ecc_simple") + macro_location = tmp_path / "macro_localtion.tcl" + macro_location.write_text("# macro locations\n") + workspace = Workspace( + config={ + StepEnum.FLOORPLAN.value: floorplan_config, + "macro_location": macro_location, + } + ) + module = FakeFloorplanModule() + + monkeypatch.setattr(ecc_runner, "EccSubFlow", FakeSubFlow) + monkeypatch.setattr(ecc_runner, "get_eda_instance", lambda **_kwargs: module) + monkeypatch.setattr( + ecc_runner, + "save_data", + lambda **kwargs: saved_steps.append(kwargs["step"].name) or True, + ) + monkeypatch.setattr( + ecc_runner, + "run_analysis", + lambda **kwargs: analyzed_steps.append(kwargs["step"].name), + ) + + assert ecc_runner.run_pre_floorplan( + workspace=workspace, + step=EccStep(name=StepEnum.PRE_FLOORPLAN.value), + ) + assert calls == [ + ("init_fp", str(simple_floorplan_config)), + ("run_simple_fp",), + ("destroy_fp",), + ] + assert json.loads(simple_floorplan_config.read_text())["macro_placer"] == { + "mode": "auto", + "file_path": "", + } + assert json.loads(floorplan_config.read_text())["macro_placer"] == { + "mode": "file", + "file_path": "old_locations.txt", + } + + assert ecc_runner.run_post_floorplan( + workspace=workspace, + step=EccStep(name=StepEnum.POST_FLOORPLAN.value), + ) + assert calls[3:] == [ + ("init_fp", str(floorplan_config)), + ("run_fp",), + ("destroy_fp",), + ] + assert json.loads(floorplan_config.read_text())["macro_placer"] == { + "mode": "file", + "file_path": str(macro_location), + } + assert saved_steps == [StepEnum.PRE_FLOORPLAN.value, StepEnum.POST_FLOORPLAN.value] + assert analyzed_steps == [StepEnum.POST_FLOORPLAN.value] + + class FakeRcxModule: def __init__(self, *, init_ok=True, run_ok=True): self.calls = [] diff --git a/test/tools/ecc_dreamplace/test_macro_only_db.py b/test/tools/ecc_dreamplace/test_macro_only_db.py new file mode 100644 index 000000000..ef7ca3d3d --- /dev/null +++ b/test/tools/ecc_dreamplace/test_macro_only_db.py @@ -0,0 +1,154 @@ +from types import SimpleNamespace + +import numpy as np +from dreamplace.macroPlaceDB import MacroPlaceDB +from dreamplace.Placer import PlacementEngine + + +def _macro_params(**overrides): + values = { + "macro_only": 1, + "bndry_padding_x": 0, + "bndry_padding_y": 0, + "macro_halo_x": 0, + "macro_halo_y": 0, + "macro_pin_halo_x": -1, + "macro_pin_halo_y": -1, + "cell_padding_x": 0, + } + values.update(overrides) + return SimpleNamespace(**values) + + +def test_macro_only_uses_native_masks_instead_of_geometry_heuristics(): + place_db = MacroPlaceDB(ecc_module=None) + place_db.num_physical_nodes = 8 + place_db.num_terminals = 4 + place_db.num_terminal_NIs = 0 + place_db.movable_slice = slice(0, 4) + place_db.fixed_slice = slice(4, 8) + place_db.node_size_x = np.array([10, 8, 500, 1, 10, 10, 10, 1000], dtype=np.float32) + place_db.node_size_y = np.array([10, 8, 10, 1, 10, 10, 10, 1000], dtype=np.float32) + place_db.node_x = np.zeros(8, dtype=np.float32) + place_db.node_y = np.zeros(8, dtype=np.float32) + place_db.node_is_hard_macro = np.array( + [True, True, False, False, True, True, True, False], dtype=np.bool_ + ) + place_db.macro_writeback_candidate = np.array( + [True, True, False, False, False, False, False, False], dtype=np.bool_ + ) + place_db.node2pin_map = np.empty(8, dtype=object) + for node_id in range(8): + place_db.node2pin_map[node_id] = np.array([node_id], dtype=np.int32) + place_db.pin2node_map = np.arange(8, dtype=np.int32) + place_db.pin_offset_x = np.zeros(8, dtype=np.float32) + place_db.pin_offset_y = np.zeros(8, dtype=np.float32) + place_db.site_width = 1.0 + place_db.row_height = 1.0 + place_db.total_space_area = 10_000_000.0 + + place_db.update_macros(_macro_params()) + + np.testing.assert_array_equal(place_db.movable_macro_mask, [True, True, False, False]) + np.testing.assert_array_equal(place_db.movable_macro_idx, [0, 1]) + np.testing.assert_array_equal(place_db.fixed_macro_mask, [True, True, True, False]) + np.testing.assert_array_equal(place_db.fixed_macro_idx, [4, 5, 6]) + + +def test_macro_only_apply_delegates_unscaled_physical_coordinates_to_native_writeback(): + class FakeNativePlaceDB: + def __init__(self): + self.calls = [] + + def write_macro_placement_back(self, node_x, node_y): + self.calls.append((node_x.copy(), node_y.copy())) + return 3 + + class FakeEccModule: + def __init__(self): + self.dense_calls = [] + + def write_placement_back(self, ecc_db, node_x, node_y): + self.dense_calls.append((ecc_db, node_x.copy(), node_y.copy())) + + ecc_module = FakeEccModule() + native_db = FakeNativePlaceDB() + place_db = MacroPlaceDB(ecc_module=ecc_module) + place_db.pydb = native_db + place_db.ecc_db = object() + place_db.num_physical_nodes = 4 + place_db.num_terminals = 0 + place_db.num_terminal_NIs = 0 + place_db.node_x = np.zeros(4, dtype=np.float32) + place_db.node_y = np.zeros(4, dtype=np.float32) + params = SimpleNamespace(macro_only=1, scale_factor=2.0, shift_factor=[10.0, 20.0]) + + place_db.apply( + params, + np.array([2.0, 4.0, 6.0, 8.0], dtype=np.float32), + np.array([8.0, 10.0, 12.0, 14.0], dtype=np.float32), + ) + + assert len(native_db.calls) == 1 + np.testing.assert_array_equal(native_db.calls[0][0], [11.0, 12.0, 13.0, 14.0]) + np.testing.assert_array_equal(native_db.calls[0][1], [24.0, 25.0, 26.0, 27.0]) + assert ecc_module.dense_calls == [] + + +def test_normal_apply_keeps_dense_writeback(): + class FakeNativePlaceDB: + def write_macro_placement_back(self, node_x, node_y): + raise AssertionError("normal placement must not use selective writeback") + + class FakeEccModule: + def __init__(self): + self.dense_calls = [] + + def write_placement_back(self, ecc_db, node_x, node_y): + self.dense_calls.append((ecc_db, node_x.copy(), node_y.copy())) + + ecc_module = FakeEccModule() + place_db = MacroPlaceDB(ecc_module=ecc_module) + place_db.pydb = FakeNativePlaceDB() + place_db.ecc_db = object() + place_db.num_physical_nodes = 2 + place_db.num_terminals = 0 + place_db.num_terminal_NIs = 0 + place_db.node_x = np.zeros(2, dtype=np.float32) + place_db.node_y = np.zeros(2, dtype=np.float32) + params = SimpleNamespace(macro_only=0, scale_factor=2.0, shift_factor=[10.0, 20.0]) + + place_db.apply( + params, + np.array([2.0, 4.0], dtype=np.float32), + np.array([8.0, 10.0], dtype=np.float32), + ) + + assert len(ecc_module.dense_calls) == 1 + assert ecc_module.dense_calls[0][0] is place_db.ecc_db + np.testing.assert_array_equal(ecc_module.dense_calls[0][1], [11.0, 12.0]) + np.testing.assert_array_equal(ecc_module.dense_calls[0][2], [24.0, 25.0]) + + +def test_macro_only_empty_candidate_set_skips_placement(): + engine = PlacementEngine.__new__(PlacementEngine) + engine.params = SimpleNamespace(macro_only=1) + engine.placedb = SimpleNamespace( + pydb=SimpleNamespace( + macro_writeback_candidate=np.array([False, False, False], dtype=np.bool_) + ) + ) + engine.setup_placedb = lambda: (_ for _ in ()).throw( + AssertionError("empty macro placement must not initialize the placement database") + ) + engine.place = lambda: (_ for _ in ()).throw( + AssertionError("empty macro placement must not run optimization") + ) + + result = engine.run() + + assert result == { + "executed": False, + "candidate_count": 0, + "reason": "no_unplaced_hard_macros", + } diff --git a/test/tools/ecc_dreamplace/test_module.py b/test/tools/ecc_dreamplace/test_module.py index 883876245..1907f309c 100644 --- a/test/tools/ecc_dreamplace/test_module.py +++ b/test/tools/ecc_dreamplace/test_module.py @@ -1,8 +1,10 @@ from pathlib import Path from types import SimpleNamespace +import pytest + from chipcompiler.data import EccData, EccStep, LogPaths, OriginDesign, StepEnum, Workspace -from chipcompiler.tools.ecc_dreamplace.module import DreamplaceModule +from chipcompiler.tools.ecc_dreamplace.module import DreamplaceModule, DreamplaceRunMode from chipcompiler.tools.ecc_dreamplace.service import get_step_info from chipcompiler.utility import json_write @@ -12,11 +14,81 @@ def fromJson(self, config): self.__dict__.update(config) +@pytest.mark.parametrize( + ("mode", "result", "expected"), + [ + ( + DreamplaceRunMode.MACRO_PLACEMENT, + { + "executed": False, + "candidate_count": 0, + "reason": "no_unplaced_hard_macros", + }, + True, + ), + (DreamplaceRunMode.MACRO_PLACEMENT, {"executed": False}, False), + (DreamplaceRunMode.PLACEMENT, {}, False), + (DreamplaceRunMode.PLACEMENT, {"hpwl": 1.0}, True), + ], +) +def test_run_accepts_only_the_defined_empty_macro_short_circuit( + monkeypatch, tmp_path, mode, result, expected +): + import dreamplace.Params as params_module + import dreamplace.Placer as placer_module + + class FakeEngine: + def __init__(self, _params): + pass + + def setup_rawdb(self, **_kwargs): + pass + + def run(self): + return result + + config_path = tmp_path / "dreamplace_ecc.json" + json_write(config_path, {}) + workspace = Workspace( + directory=tmp_path / "workspace", + design=OriginDesign(name="gcd"), + config={"dreamplace": config_path}, + ) + step = EccStep( + name=( + StepEnum.MACRO_PLACEMENT.value + if mode is DreamplaceRunMode.MACRO_PLACEMENT + else StepEnum.PLACEMENT.value + ), + data=EccData( + dir=tmp_path / "data", + steps={ + StepEnum.MACRO_PLACEMENT.value: tmp_path / "data" / "macro", + StepEnum.PLACEMENT.value: tmp_path / "data" / "pl", + }, + ), + ) + module = DreamplaceModule( + workspace=workspace, + step=step, + ecc_module=object(), + input_def=tmp_path / "input.def", + input_verilog=tmp_path / "input.v", + output_def=tmp_path / "output.def", + output_verilog=tmp_path / "output.v", + ) + monkeypatch.setattr(params_module, "Params", FakeParams) + monkeypatch.setattr(placer_module, "PlacementEngine", FakeEngine) + + assert module._run(mode=mode) is expected + + def test_build_params_preserves_routability_config_and_forces_timing_off(tmp_path): config_path = tmp_path / "dreamplace_ecc.json" json_write( config_path, { + "macro_only": 1, "routability_opt_flag": 1, "get_congestion_map": 1, "with_sta": True, @@ -46,10 +118,11 @@ def test_build_params_preserves_routability_config_and_forces_timing_off(tmp_pat output_verilog=tmp_path / "output.v", ) - params = module._build_params(FakeParams, legalize_only=False) + params = module._build_params(FakeParams, mode=DreamplaceRunMode.PLACEMENT) assert params.routability_opt_flag == 1 assert params.get_congestion_map == 1 + assert params.macro_only == 0 assert params.with_sta is False assert params.timing_opt_flag == 0 assert params.timing_eval_flag == 0 @@ -84,12 +157,106 @@ def test_build_params_uses_empty_strings_for_missing_inputs(tmp_path): output_verilog=tmp_path / "output.v", ) - params = module._build_params(FakeParams, legalize_only=False) + params = module._build_params(FakeParams, mode=DreamplaceRunMode.PLACEMENT) assert params.def_input == "" assert params.verilog_input == "" +def test_macro_placement_forces_selective_non_routable_placement_params(tmp_path): + config_path = tmp_path / "dreamplace_ecc.json" + json_write( + config_path, + { + "macro_only": 0, + "global_place_flag": 0, + "macro_place_flag": 0, + "legalize_flag": 0, + "two_stage_flag": 1, + "routability_opt_flag": 1, + "get_congestion_map": 1, + "egr_padding_flag": 1, + }, + ) + workspace = Workspace( + directory=str(tmp_path / "workspace"), + design=OriginDesign(name="gcd"), + config={"dreamplace": config_path}, + ) + step = EccStep( + name=StepEnum.MACRO_PLACEMENT.value, + data=EccData( + dir=tmp_path / "data", + steps={StepEnum.MACRO_PLACEMENT.value: tmp_path / "data" / "macro"}, + ), + ) + module = DreamplaceModule( + workspace=workspace, + step=step, + ecc_module=None, + input_def=tmp_path / "input.def", + input_verilog=tmp_path / "input.v", + output_def=tmp_path / "output.def", + output_verilog=tmp_path / "output.v", + ) + + params = module._build_params(FakeParams, mode=DreamplaceRunMode.MACRO_PLACEMENT) + + assert { + "macro_only": params.macro_only, + "global_place_flag": params.global_place_flag, + "macro_place_flag": params.macro_place_flag, + "legalize_flag": params.legalize_flag, + "two_stage_flag": params.two_stage_flag, + "macro_halo_x": params.macro_halo_x, + "macro_halo_y": params.macro_halo_y, + "routability_opt_flag": params.routability_opt_flag, + "get_congestion_map": params.get_congestion_map, + "egr_padding_flag": params.egr_padding_flag, + } == { + "macro_only": 1, + "global_place_flag": 1, + "macro_place_flag": 1, + "legalize_flag": 1, + "two_stage_flag": 0, + "macro_halo_x": 2000, + "macro_halo_y": 2000, + "routability_opt_flag": 0, + "get_congestion_map": 0, + "egr_padding_flag": 0, + } + + +def test_legalization_forces_macro_only_off(tmp_path): + config_path = tmp_path / "dreamplace_ecc.json" + json_write(config_path, {"macro_only": 1}) + workspace = Workspace( + directory=str(tmp_path / "workspace"), + design=OriginDesign(name="gcd"), + config={"dreamplace": config_path}, + ) + step = EccStep( + name=StepEnum.LEGALIZATION.value, + data=EccData( + dir=tmp_path / "data", + steps={StepEnum.LEGALIZATION.value: tmp_path / "data" / "pl"}, + ), + ) + module = DreamplaceModule( + workspace=workspace, + step=step, + ecc_module=None, + input_def=tmp_path / "input.def", + input_verilog=tmp_path / "input.v", + output_def=tmp_path / "output.def", + output_verilog=tmp_path / "output.v", + ) + + params = module._build_params(FakeParams, mode=DreamplaceRunMode.LEGALIZATION) + + assert params.macro_only == 0 + + def test_dreamplace_step_info_stringifies_path_config(tmp_path): workspace = Workspace( directory=tmp_path, @@ -135,9 +302,9 @@ def _module_for_owner(tmp_path, step_name: str) -> DreamplaceModule: def test_run_legalization_allows_timing_opt_and_legalization_owners(tmp_path, monkeypatch): seen: list[str] = [] - def fake_run(self, *, legalize_only: bool) -> bool: + def fake_run(self, *, mode: DreamplaceRunMode) -> bool: seen.append(self.step.name) - assert legalize_only is True + assert mode is DreamplaceRunMode.LEGALIZATION return True monkeypatch.setattr(DreamplaceModule, "_run", fake_run) @@ -156,12 +323,22 @@ def test_timing_opt_legalize_log_does_not_reuse_step_log(tmp_path): legalization = _module_for_owner(tmp_path, StepEnum.LEGALIZATION.value) timing_opt = _module_for_owner(tmp_path, StepEnum.TIMING_OPT.value) - assert legalization._file_handler_path(legalize_only=True) == str(tmp_path / "step.log") - assert timing_opt._file_handler_path(legalize_only=True) == str( + assert legalization._file_handler_path(mode=DreamplaceRunMode.LEGALIZATION) == str( + tmp_path / "step.log" + ) + assert timing_opt._file_handler_path(mode=DreamplaceRunMode.LEGALIZATION) == str( Path(timing_opt.result_dir) / "dreamplace_legalization.log" ) +def test_macro_placement_log_does_not_reuse_step_log(tmp_path): + macro_placement = _module_for_owner(tmp_path, StepEnum.MACRO_PLACEMENT.value) + + assert macro_placement._file_handler_path(mode=DreamplaceRunMode.MACRO_PLACEMENT) == str( + Path(macro_placement.result_dir) / "dreamplace_macro_placement.log" + ) + + def test_dreamplace_run_step_ignores_timing_opt(tmp_path, monkeypatch): from chipcompiler.tools.ecc_dreamplace import runner as dreamplace_runner diff --git a/test/tools/ecc_dreamplace/test_runner.py b/test/tools/ecc_dreamplace/test_runner.py new file mode 100644 index 000000000..8552ad160 --- /dev/null +++ b/test/tools/ecc_dreamplace/test_runner.py @@ -0,0 +1,119 @@ +from chipcompiler.data import EccStep, StateEnum, StepEnum, Workspace +from chipcompiler.tools.ecc_dreamplace import runner as dreamplace_runner + + +class FakeSubFlow: + def __init__(self, **_kwargs): + self.updates = [] + + def update_step(self, **kwargs): + self.updates.append(kwargs) + + +def test_macro_placement_step_runs_and_saves_outputs(monkeypatch, tmp_path): + calls = [] + subflow = FakeSubFlow() + + class FakeDreamplaceModule: + def __init__(self, **kwargs): + assert kwargs["step"].name == StepEnum.MACRO_PLACEMENT.value + calls.append("init") + + def run_macro_placement(self): + calls.append("run") + return True + + class FakeEccModule: + def tcl_save(self, output_path): + calls.append(("tcl_save", output_path)) + return True + + macro_location = tmp_path / "macro_localtion.tcl" + module = FakeEccModule() + step = EccStep(name=StepEnum.MACRO_PLACEMENT.value) + + monkeypatch.setattr(dreamplace_runner, "EccSubFlow", lambda **_kwargs: subflow) + monkeypatch.setattr( + dreamplace_runner.ecc_runner, + "get_eda_instance", + lambda **_kwargs: module, + ) + monkeypatch.setattr( + dreamplace_runner.ecc_runner, + "save_data", + lambda **kwargs: calls.append(("save", kwargs["step"].name)) or True, + ) + monkeypatch.setattr(dreamplace_runner, "DreamplaceModule", FakeDreamplaceModule) + + assert ( + dreamplace_runner.run_macro_placement( + Workspace(config={"macro_location": macro_location}), step + ) + is True + ) + assert calls == [ + "init", + "run", + ("tcl_save", macro_location), + ("save", StepEnum.MACRO_PLACEMENT.value), + ] + assert [update["step_name"] for update in subflow.updates] == [ + "load data", + "macro placement", + "save data", + ] + assert subflow.updates[1]["state"] is StateEnum.Success + + +def test_macro_placement_stops_when_tcl_handoff_fails(monkeypatch, tmp_path): + calls = [] + subflow = FakeSubFlow() + + class FakeDreamplaceModule: + def __init__(self, **_kwargs): + pass + + def run_macro_placement(self): + calls.append("run") + return True + + class FakeEccModule: + def tcl_save(self, output_path): + calls.append(("tcl_save", output_path)) + return False + + macro_location = tmp_path / "macro_localtion.tcl" + step = EccStep(name=StepEnum.MACRO_PLACEMENT.value) + + monkeypatch.setattr(dreamplace_runner, "EccSubFlow", lambda **_kwargs: subflow) + monkeypatch.setattr( + dreamplace_runner.ecc_runner, + "get_eda_instance", + lambda **_kwargs: FakeEccModule(), + ) + monkeypatch.setattr( + dreamplace_runner.ecc_runner, + "save_data", + lambda **_kwargs: calls.append("save") or True, + ) + monkeypatch.setattr(dreamplace_runner, "DreamplaceModule", FakeDreamplaceModule) + + assert ( + dreamplace_runner.run_macro_placement( + Workspace(config={"macro_location": macro_location}), step + ) + is False + ) + assert calls == ["run", ("tcl_save", macro_location)] + assert [update["step_name"] for update in subflow.updates] == ["load data", "macro placement"] + assert subflow.updates[-1]["state"] is StateEnum.Imcomplete + + +def test_run_step_dispatches_macro_placement(monkeypatch): + monkeypatch.setattr(dreamplace_runner, "is_eda_exist", lambda: True) + monkeypatch.setattr(dreamplace_runner, "run_macro_placement", lambda **_kwargs: True) + + assert ( + dreamplace_runner.run_step(Workspace(), EccStep(name=StepEnum.MACRO_PLACEMENT.value)) + is True + ) diff --git a/test/yosys_lec/test_tools_yosys_lec.py b/test/yosys_lec/test_tools_yosys_lec.py index 33cf746a0..76762cdd2 100644 --- a/test/yosys_lec/test_tools_yosys_lec.py +++ b/test/yosys_lec/test_tools_yosys_lec.py @@ -439,13 +439,15 @@ def test_rtl2gds_flow_runs_post_route_lec_after_lvs_before_drc(): assert steps[lec_index] == (StepEnum.POST_ROUTE_LEC, "yosys_lec", StateEnum.Unstart) -def test_rtl2gds_flow_runs_synthesis_lec_before_floorplan(): +def test_rtl2gds_flow_runs_synthesis_lec_before_pre_floorplan(): from chipcompiler.rtl2gds import build_rtl2gds_flow steps = build_rtl2gds_flow() step_names = [step[0] for step in steps] lec_index = step_names.index(StepEnum.LEC) - assert step_names.index(StepEnum.SYNTHESIS) < lec_index < step_names.index(StepEnum.FLOORPLAN) + assert ( + step_names.index(StepEnum.SYNTHESIS) < lec_index < step_names.index(StepEnum.PRE_FLOORPLAN) + ) assert steps[lec_index] == (StepEnum.LEC, "yosys_lec", StateEnum.Unstart)