Manual · Deep Dive

The Flow Blueprint — inside phase1_phase2_phase3.yaml

One YAML file declares every step Vibe-IC can run — its dependencies, its required outputs, and the exact command that verifies it. flow_compliance_check.py parses this file and executes those commands literally: it is the real, running definition of the 63×8 gate matrix. What it is NOT is a program the EDA tools themselves read at execution time — that distinction, and where it has actually mattered, is the point of this page.

4,420lines, single file
63step entries
8stages
8judgment dimensions per step
173programs that must independently agree with it
01

What this file is — and, precisely, what it is not

phase1_phase2_phase3.yaml is the single canonical declaration of every step Vibe-IC can run: its identity, what it depends on, what it must produce, and the literal command that checks it. There is exactly one acceptance criterion for the whole flow — flow_compliance_check.py . --strict must exit 0 — and that program's job is to read this file and answer, for all 63 declared steps, whether reality matches the declaration.

On the audit side, the file is genuinely executable

This is not a metaphor. Each step can carry a gate: block whose value is a literal shell invocation, with the target file path written directly into the argument string. flow_compliance_check.py reads that string and runs it. Step 31 (Physical Verification) declares its DRC sign-off check like this — copied verbatim from the shipped flow definition:

# flow/phase1_phase2_phase3.yaml — step 31
required_outputs:
  - "reports/phase3/drc_signoff.rpt"
  - "reports/phase3/lvs.rpt"
gate:
  all_of:
    - program_exit_zero: "drc_vacuous_pass_check . --under reports/phase3/drc_signoff.rpt --json ..."

The same path string appears in required_outputs and again as a literal --under argument in the gate command that actually runs. That is the file being the executable map: a maintainer reading the yaml sees exactly which artefact step 31's sign-off is scoped to, and so does the code that runs it — both read the same one string.

On the producer side, no runner reads this file at all

The tools that actually do the work — synthesis, place-and-route, the sign-off checks — are driven by separate orchestrator programs (phase3_one_shot_runner.py and five siblings), and those orchestrators do not load this yaml at runtime. This is stated directly in the orchestrator's own source comment, with the grep command a reader can rerun:

# programs/phase3_one_shot_runner.py
# flow/phase1_phase2_phase3.yaml declares four checkers on the two post-route
# sign-off steps. None of them was ever executed automatically. The phase-3
# orchestrators never load the flow yaml at all —
#   grep -n 'phase1_phase2_phase3.yaml|FLOW_YAML' over phase3_one_shot_runner /
#   vibe_ic_one_shot_runner / phase23_one_shot_runner returns nothing.
# A declaration is not a wiring.
What this actually cost

Because a declaration is not a wiring, three declared sign-off gates (post-route STA, the STA-corner completeness check, and the electromigration check) went unexecuted for a period. Measured on the 14 published phase-3 run-roots before the fix: sta_report_check FAILed 9/14, sta_corner_record_completeness_check FAILed 6/14, em_report_check FAILed 6/14 — real findings, sitting undetected in the corpus. One cell was published as clean while reporting −1.710 ns of setup violation at its own sign-off corner.

The fix was not a yaml edit. A separate table, _DECLARED_SIGNOFF_GATES, was added inside the runner itself, mirroring the yaml's gate paths — and a dedicated test, test_sta_gate_step_scope.py, exists for one reason: to fail the moment the runner's copy and the yaml's copy of the same path drift apart.

So the precise statement is: this file is the single source of truth for what SHOULD be true, and it genuinely drives the audit/gate layer that checks it. It does not drive the producer layer that writes the actual bytes — producers are independently coded to conform to what it declares, and history shows that conformance has to be actively maintained, not assumed.

02

Anatomy of one step entry

Every one of the 63 entries in the steps: list is a YAML mapping built from the same field vocabulary. Counted directly across all 63 entries:

id63 / 63
name63 / 63
stage63 / 63
gate62 / 63
blocks_on62 / 63
required_outputs61 / 63
required_inputs56 / 63
programs43 / 63
skills41 / 63
mcp_tools23 / 63
condition22 / 63
closed_loop19 / 63
notes15 / 63
known_gap1 / 63
fieldmeaning
id / name / stageIdentity. id is what every other step's blocks_on / required_inputs points at; stage is which of the 8 stages (below) this step belongs to.
required_inputsfrom: <step id> + path: <glob> — a dependency edge that names WHICH upstream step's WHICH artefact this step reads, not just an ordering hint.
required_outputsThe declared deliverables, as project-relative globs. This is what dimension D3 checks for existence and D7 checks for completeness (see below).
gateThe literal, executable verification — usually all_of: a list of program_exit_zero: "<command>" entries. This is what flow_compliance_check.py actually runs.
conditionfiles_exist: [...] — when absent, the step is SKIPPED-CONDITION rather than MISSING. This is what dimension D6 audits for abuse.
blocks_onOrdering edges only — step A runs after step B. It does NOT say A reads a specific artefact of B; that is what required_inputs is for. Dimension D5 checks whether blocks_on itself is declared correctly.
closed_loopfallback_to + trigger — which step to re-enter, and under what failure condition, for an ECO-style retry loop (e.g. step 31 DRC/LVS failing falls back to step 32's repair pass).
skills / mcp_tools / programsWhich Claude skill, which vibeic-eda MCP tool, and which helper program this step is associated with — the routing a fresh session uses to know what to invoke.

Read side by side: the same step through two lenses

Step 9 — Synthesis — shows the declaration/dependency half of the vocabulary:

- id: 9
  name: "Synthesis (Yosys → mapped netlist)"
  stage: stage2
  required_inputs:
    - from: 1
      path: "phase2/stage1/rtl/*.sv OR phase2/stage1/rtl/*.v"
    - from: 7
      path: "phase2/stage2/constraints/*.sdc"
  skills: [synth-doctor]
  mcp_tools: [eda_synth]
  required_outputs:
    - "phase2/stage2/synth/netlist.v"
    - "phase2/stage2/synth/area.rpt OR phase2/stage2/synth/stats.json"
03

8 stages, 63 steps

The file's own metadata declares total_steps: 44 and analog_steps: 9 — the 44 numbered core steps (RTL generation through manufacturing) plus the 9 analog steps. The remaining 10 entries are special-id steps that sit outside the numbered core: the Phase-1 front door (D1), the structural pre-flight umbrella (P0), three at-speed delay-fault grading steps (DT1–DT3), one functional-safety step (FS1), and the four mixed-signal integration steps (M1–M4). 44 + 9 + 1 + 1 + 3 + 1 + 4 = 63.

stage_phase1 Phase 1 — Spec Extraction (input → L1–L27 JSON) D1 · 1 step
stage1 RTL Generation + Verification 1–6, P0 · 7 steps
stage2 Synthesis + DFT 7–14, FS1, DT1–3 · 12 steps
stage_analog Analog Design Pipeline (parallel with stages 1–2) A1–A9 · 9 stepscondition: phase1/analog/analog_block_list.json exists
stage3 Physical Design + Sign-off 15–32 · 18 steps
stage_mixed_signal Mixed-Signal Integration (A+D top-level merge + sign-off) M1–M4 · 4 stepscondition: phase1/analog/analog_block_list.json exists
stage4 Output + Validation 33–39 · 7 steps
stage5_manufacturing Manufacturing & Test (fab → wafer-sort → packaging → ATE final) 40–44 · 5 stepscondition: phase3/stage5_manufacturing/silicon_received.json exists

A single final_gate closes the whole file: program flow_compliance_check, args . --flow phase1_phase2_phase3 --strict, exit_zero_required: true — one program, one command, one binary answer.

04

The 8 judgment dimensions — 63 × 8 = 504 cells

A step passing its own gate is one fact. Whether that gate is even reachable, whether its declared outputs list is honest, whether a skip is legitimate — those are separate questions this file's structure makes it possible to ask independently. Dimensions 1–7 each interrogate one property of a single step's declaration; dimension 8 asks a cross-cutting question — when a declared output never lands, does SOMETHING catch it.

D1Wiring接線Is the gate actually invoked through one of its three call channels?
D2Runnable跑得動Does it actually execute and return a real exit code — and can that code ever be non-zero?
D3Outputs產出Do the required_outputs really exist on disk, and are they non-empty?
D4Criteria判準Does what the gate actually measures match what it claims to measure?
D5Deps依賴Is the blocks_on ordering declaration itself correct?
D6SkipSkipIs the SKIPPED-CONDITION escape hatch being abused?
D7List清單Is the required_outputs list itself complete — does it name everything the step should be judged on?
D8Catcher抓漏When a declared output never materializes, is there a mechanism that is actually supposed to notice — and does it?

These eight questions, run against all 63 steps, are what the live Flow Gate Dashboard tracks continuously — including which cells are ENFORCED, which are WAIVED with a recorded reason, and which are structurally NA. It is the running instrument this page describes the blueprint of.

05

Reading a step: declared, wired, or both

Because the audit side executes this file and the producer side does not, a step's real status has two independent questions, not one — and a reader who only checks the first can be fooled.

questionanswered byif the answer is no
Is it declared?This yaml carries a gate: block for the step.The step has no defined acceptance criterion at all — a gap the yaml itself makes visible.
Is it wired?Some caller — flow_compliance_check.py, or an inline invocation inside an orchestrator — actually runs the declared gate on a real project.A declaration with nothing behind it — the step 23/25 case above: three real sign-off findings sat undetected because the yaml said the check applied and nothing ever asked it.

Dimension D1 (Wiring) exists specifically to make the second question a tracked, per-step fact instead of something a reader has to independently rediscover.

06

The steps/ folder is derived FROM this file — it is not what any gate reads

A published or in-progress run also carries a steps/<phase>/<stage>/<id>_<slug>/ folder tree, nested by exactly the id / stage fields this page just described. It is easy to assume THAT folder is the source of truth a gate checks against. It is not — it is generated FROM this yaml's own declarations, after the fact, for a human or an auditor to browse.

Measured, not assumed

On a real converged run, deleting step 3's own declared output symlink inside its steps/ folder changed nothing — PASS=4 FAIL=3 MISSING=0 before and after — because nothing reads that folder's files; they are absolute symlinks nobody follows. What DOES move the verdict: deleting the canonical file the symlink points at (→ MISSING, a spare copy elsewhere does not save it), truncating it to 0 bytes (→ MISSING), or removing the written.json observation records entirely (→ falls back to a project-wide glob, which turns FAIL rather than MISSING).

Of the 8 dimensions above, only D3 (Outputs) ever reads a step's written.json record at all. The other seven never reference it. The record is decided by this yaml's required_outputs declaration, re-resolved live against the real files — never by the folder shape.

The practical takeaway: this yaml file is the map. The steps/ folder is a photograph of that map taken after a run, useful for browsing but never load-bearing on its own. If the two ever disagree, this file wins — and the D1/D3 dimensions above exist to make sure a disagreement is a visible, tracked cell rather than a silent one.