Files
secs-gem/docs/16_e90_e157_substrates_modules.md
raphael 8a55137e57 feat(client): typo-safe protocol enums + context manager; add wafer_tool example
Interface cleanup so the report_* family matches the typo-safe ethos of
eq.names instead of leaking raw protobuf errors on a misspelled value.

- Milestone / ModuleState / JobState: importable str-enums (member == its
  wire name, so plain strings still work) — autocomplete + a typo-checked
  happy path. The clean rule: equipment-specific *names* live on eq.names;
  fixed protocol *value-sets* are enums.
- _enum_value(): resolves an enum-or-string arg client-side and, on a bad
  value, raises ValueError with a close-match hint *before* the wire. Wired
  into report_job / report_substrate / report_module / request_control_state
  (all previously raised a raw protobuf ValueError).
- Equipment is now a context manager (with Equipment(...) as eq: ...).
- examples/wafer_tool.py: a cluster tool tracking one wafer through one
  module end-to-end (E90 + E157), showing the enums + context manager.
- tests/test_enums.py: asserts the enums stay in lockstep with the proto and
  that the typo path is helpful. Wired into run_interop.sh (pyclient step).
- Interop drives both the enum and string forms on the wire + the ValueError
  typo path. Docs (ch16/ch42) updated; names-vs-enums rule documented.

All Python unit tests + 25 pyclient interop checks pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:09:48 +02:00

8.1 KiB
Raw Permalink Blame History

16 — E90 + E157: Substrate and module tracking

15 E87 — Carriers and load ports | Back to index | Next: 17 E116 + E120 + E39 — Performance, CEM, objects

E87 (chapter 15) tracks the container. E90 and E157 track what's inside the container — every individual wafer (substrate) and every process module the wafer passes through.

This chapter is shorter than the others in Part 2 because the ideas overlap E87 (the same three-orthogonal-axes pattern repeats) and E40 (state events drive S6F11 CEIDs).


E90 — Substrate tracking

What it tracks

One state-bearing record per wafer, identified by a substrate ID (an ASCII string, often the laser-etched serial number).

E90 has three orthogonal axes — the same pattern as E87's carrier (chapter 15 §3):

1. Substrate State (STS) — location

// include/secsgem/gem/substrate_state.hpp:26
enum class SubstrateState : uint8_t {
  AtSource         = 0,    // in its origin carrier slot
  AtWork           = 1,    // in-process at a module
  AtDestination    = 2,    // delivered to final location
  NoState          = 255,
};

Events: Acquire (Source→Work), Release (Work→Destination), Return (Work→Source, for unprocessed return).

2. Substrate Processing State (SPS) — lifecycle

enum class SubstrateProcessingState : uint8_t {
  NeedsProcessing = 0,
  InProcess       = 1,
  Processed       = 2,
  Aborted         = 3,
  Stopped         = 4,
  Rejected        = 5,
  Lost            = 6,
  Skipped         = 7,
  NoState         = 255,
};

Events: StartProcessing, EndProcessing, Abort, Stop, Reject, ReportLost, Skip.

3. Substrate ID Status — identity confidence

enum class SubstrateIDStatus : uint8_t {
  NotConfirmed   = 0,
  WaitingForHost = 1,
  Confirmed      = 2,
  Mismatched     = 3,
  NoState        = 255,
};

Mirrors the CarrierIDStatus pattern from E87 — same problem (equipment reads ID, host may need to verify), same shape of solution.

Why three axes?

The same reason E87 has three: these aspects evolve at different times and for different reasons.

  • A wafer can be AtWork + NeedsProcessing (just arrived, recipe hasn't started).
  • A wafer can be AtWork + InProcess (recipe running).
  • A wafer can be AtSource + Processed + Confirmed (back in its carrier slot after processing — typical end state).

Putting all three in one enum would multiply to ~30 valid combinations. Three independent FSMs with ~3 events each is much cleaner.

Code

State machines: include/secsgem/gem/substrate_state.hpp defines SubstrateStateMachine, which composes the three.

Store: include/secsgem/gem/store/substrates.hpp holds one record per substrate ID, with a Location string the application updates as the wafer moves.

Tests: tests/test_substrates.cpp (14 cases — every axis, every event); persistence in tests/test_substrate_persistence.cpp (7 cases).

CEID-on-wire emission ("Substrate StartProcessing fires the configured SubstrateInProcess CEID") is verified by tests/test_wire_ceid_emission.cpp.

Wire interaction

E90 doesn't define its own S/F messages — substrate state changes fire as CEIDs that the host has subscribed to via the standard E30 §6.6 Dynamic Event Report Configuration (chapter 13). So:

  • Equipment fires Acquire event on substrate W-2026-06-09-A47.
  • SubstrateStateMachine transitions Source → Work.
  • The state-change handler looks up the configured CEID for "SubstrateInProcess" (from data/equipment.yaml).
  • That CEID fires → compose_reports_for(ceid)S6F11.

Host gets one S6F11 per wafer transition. In a 25-wafer FOUP that's 2550 events per processing pass. Persistent reports + spool (chapter 13 Additionals) handle the burst.


E157 — Module Process Tracking

What it tracks

One state-bearing record per process module. A cluster tool has multiple modules (Chamber A, Chamber B, Pre-clean, …); each runs its own recipe step in parallel or sequence. E157 lets the host see which module is in which step of which recipe right now.

The states

// include/secsgem/gem/module_state.hpp:20
enum class ModuleState : uint8_t {
  NotExecuting     = 0,
  GeneralExecuting = 1,   // setup, pre-process, post-process
  StepExecuting    = 2,   // actively running a recipe step
  StepCompleted    = 3,
  NoState          = 255,
};

Events: StartGeneral, StartStep, CompleteStep, Reset, Abort.

Notice this is a much simpler FSM than E90 — one axis only. That's because modules are more deterministic than substrates: a module is either running a step or it isn't; substrates can be in many overlapping conditions.

Code

include/secsgem/gem/module_state.hpp defines ModuleStateMachine.

Store: include/secsgem/gem/store/modules.hpp.

Tests: tests/test_modules.cpp (5 cases).

How E157 plays with E40 and E90

Concrete example. A PVD tool with three modules (Chamber A, B, C); host submits PJ for wafer W-1, recipe says "process at Chamber B for 90 seconds":

1. PJ-1 transitions Queued → SettingUp → WaitingForStart → Processing.
   (E40 FSM, chapter 14)
2. Equipment fires ModuleEvent::StartGeneral on Chamber B.
   ModuleState: NotExecuting → GeneralExecuting.
   (E157 FSM)
3. Equipment fires SubstrateEvent::Acquire on W-1.
   Substrate STS: AtSource → AtWork.
   (E90 FSM)
4. Recipe step begins.  ModuleEvent::StartStep on Chamber B.
   ModuleState: GeneralExecuting → StepExecuting.
   Substrate SPS: NeedsProcessing → InProcess.
5. ...90 seconds pass...
6. Recipe step ends.  ModuleEvent::CompleteStep on Chamber B.
   ModuleState: StepExecuting → StepCompleted.
   Substrate SPS: InProcess → Processed.
7. Substrate released.  SubstrateEvent::Release on W-1.
   Substrate STS: AtWork → AtDestination.
8. PJ-1: ProcessComplete.

Each of the eight transitions fires a CEID, which fires an S6F11 event report. The host sees the complete trace of where every wafer was at every moment.


Daemon path (Python client)

If your tool uses the daemon (secs_gemd) and the Python client, the E90 and E157 RPCs are wrapped as two methods:

from secsgem_client import Equipment, Milestone, ModuleState
eq = Equipment("localhost:50051")

# E90 — substrate journey (daemon drives FSMs, fires CEIDs automatically)
eq.report_substrate("WFR-001", Milestone.ARRIVED, carrier_id="FOUP-7", slot=3)
eq.report_substrate("WFR-001", Milestone.AT_WORK)
eq.report_substrate("WFR-001", Milestone.PROCESSING)
eq.report_substrate("WFR-001", Milestone.PROCESSED)
eq.report_substrate("WFR-001", Milestone.AT_DESTINATION)

# E157 — module state (module is auto-created on first report)
eq.report_module("CHAMBER-A", ModuleState.GENERAL_EXECUTING)
eq.report_module("CHAMBER-A", ModuleState.STEP_EXECUTING)
eq.report_module("CHAMBER-A", ModuleState.STEP_COMPLETED)
eq.report_module("CHAMBER-A", ModuleState.NOT_EXECUTING)

Milestone / ModuleState are importable enums (each member equals its plain-string name, so "ARRIVED" works just as well). The daemon's ReportSubstrate handler validates FSM transitions: a substrate that never ARRIVED is rejected with INVALID_OBJECT, and a duplicate ARRIVED with CANNOT_DO_NOW (substrate '...' already exists) — it never silently re-creates over a wafer's live state. A complete worked example is clients/python/examples/wafer_tool.py.


Where to go next

You now know how every component of in-flight material is tracked. The next chapter covers the three smaller GEM 300 standards that round out the suite: equipment performance time tracking, the common equipment model, and generic object services.

Next: → 17 E116 + E120 + E39 — Performance, CEM, objects