From 40df3067a44b69929a8f4c616d4c77ddbe6ceb20 Mon Sep 17 00:00:00 2001 From: Raphael Maenle Date: Tue, 9 Jun 2026 20:14:42 +0200 Subject: [PATCH] =?UTF-8?q?docs:=20chapters=2014=E2=80=9319=20=E2=80=94=20?= =?UTF-8?q?GEM=20300=20standards=20(Part=202=20complete)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Six more chapters finishing Part 2. Together with chapters 10–13 they document every SEMI standard this codebase implements. 14 — E40 + E94: process jobs (8-state lifecycle, S16F11/F5/F7/F9 on the wire) and control jobs (CJ wraps PJs with batch policy, S14F9/S16F27 messages). Worked cascade showing how CJSTART propagates through the PJ FSM and triggers S6F11 CEIDs at each transition. 15 — E87 carriers: three orthogonal sub-machines (CarrierID, SlotMap, CarrierAccess) per carrier and three more (Transfer, Reservation, Association) per load port. S3F17 CarrierAction strings + CAACK codes, S3F19 SlotMap verify, the 5-state slot encoding, multi-port concurrency. 16 — E90 + E157: substrate tracking via three orthogonal axes (STS / SPS / SubstrateIDStatus) and module process tracking (NotExecuting / GeneralExecuting / StepExecuting / StepCompleted). End-to-end PVD example showing E40 + E157 + E90 transitions cascading into CEIDs. 17 — E116 + E120 + E39: equipment performance time-buckets across six states, common equipment model object hierarchy, S14F1/F3 GetAttr/SetAttr as the uniform wire access for any object type across multiple standards. 18 — E84 parallel I/O: ten signal lines, the 9-state handshake FSM, the three TA1/TA2/TA3 timing-critical timers, why a physical handshake gets modeled in software (testability, timer enforcement, CEID emission, multi-port concurrency), the pure-FSM + asio-adapter split. 19 — E42 + E148 + S5F9–F18: formatted recipes (S7F23/F25 typed PPBODY), time synchronization with 16-char + 14-char accepted on set, exception recovery as a persistent multi-step host-supervised FSM (Posted → Recovering → Cleared with abort/retry). Revisits the auto-S9 family and contrasts S9 (transport) vs S5F9 (application). Co-Authored-By: Claude Opus 4.7 --- docs/14_e40_e94_jobs.md | 296 ++++++++++++++++++++++++ docs/15_e87_carriers.md | 306 +++++++++++++++++++++++++ docs/16_e90_e157_substrates_modules.md | 212 +++++++++++++++++ docs/17_e116_e120_e39_objects.md | 229 ++++++++++++++++++ docs/18_e84_parallel_io.md | 280 ++++++++++++++++++++++ docs/19_e42_e148_s9_misc.md | 264 +++++++++++++++++++++ 6 files changed, 1587 insertions(+) create mode 100644 docs/14_e40_e94_jobs.md create mode 100644 docs/15_e87_carriers.md create mode 100644 docs/16_e90_e157_substrates_modules.md create mode 100644 docs/17_e116_e120_e39_objects.md create mode 100644 docs/18_e84_parallel_io.md create mode 100644 docs/19_e42_e148_s9_misc.md diff --git a/docs/14_e40_e94_jobs.md b/docs/14_e40_e94_jobs.md new file mode 100644 index 0000000..1815cc3 --- /dev/null +++ b/docs/14_e40_e94_jobs.md @@ -0,0 +1,296 @@ +# 14 — E40 + E94: Process jobs and Control jobs + +← [13 E30 — GEM](13_e30_gem.md) | [Back to index](00_index.md) | Next: [15 E87 — Carriers and load ports](15_e87_carriers.md) → + +A modern fab tool doesn't just "process wafers" — it executes +**jobs** with explicit lifecycles that the MES can submit, +monitor, pause, abort, and audit. Two SEMI standards govern this: + +- **E40** (1999) — Process Jobs. A PJ describes one *recipe run* + on a defined set of material. "Run RECIPE-Cu-A on this list of + 25 wafers." +- **E94** (2001) — Control Jobs. A CJ wraps a *batch* of PJs with + a processing policy. "Run these 4 PJs in order; abort the rest + if any one fails." + +In production the host almost always creates a CJ wrapping its +PJs and uses CJ commands (CJSTART, CJPAUSE) to drive the batch. +Per-PJ commands (PJSTART) exist but are less common. + +This chapter walks both lifecycles, the messages that drive them, +the FSMs in code, and how they cascade. + +--- + +## E40 — Process Jobs + +### The PJ lifecycle + +Eight states. Values match the **PRJOBSTATE byte** that S16F9 +carries on the wire (E40-0705 §10.3.2): + +| Value | State | Meaning | +|-------|--------------------|--------------------------------------------------------| +| 0 | `Queued` | Created; awaiting selection by a CJ or by S16F5 SELECT.| +| 1 | `SettingUp` | Equipment loading the recipe + verifying material. | +| 2 | `WaitingForStart` | Ready; awaiting PJSTART (or auto-start if configured). | +| 3 | `Processing` | Recipe running. | +| 4 | `ProcessComplete` | Recipe finished; awaiting host dequeue. | +| 5 | `Paused` | Mid-process pause; resumable. | +| 6 | `Stopping` | Graceful abort in progress. | +| 7 | `Aborting` | Forceful abort in progress. | +| 255 | `NoState` | Sentinel: "doesn't exist yet / freshly deleted." | + +``` +Created ───► Queued ──Select──► SettingUp ──SetupComplete──► WaitingForStart + │ + PJSTART ──────────┤ + ▼ + Processing + ╱ │ ╲ + PJPAUSE PJSTOP PJABORT + ╱ │ ╲ + Paused Stopping Aborting + │ │ │ + PJRESUME ▼ ▼ + ╲ ProcessComplete AbortComplete + ╲ │ + ╲ ▼ + back to Processing +``` + +Defined in +[`include/secsgem/gem/process_job_state.hpp`](../include/secsgem/gem/process_job_state.hpp). + +Drivers of the FSM (`ProcessJobEvent`): + +- **`Created`** — synthetic observer signal when the store first + records a PJ. Doesn't appear in the transition table. +- **`Select`** — `Queued → SettingUp`. Fires when a CJ promotes + this PJ for processing, or when S16F5 SELECT arrives. +- **`SetupComplete`** — equipment-internal (recipe loaded, material + verified). +- **`Start` / `Pause` / `Resume` / `Stop` / `Abort`** — host-driven + via `S16F5 PRCMD` strings. PRCMD = `"PJSTART"`, `"PJPAUSE"`, + `"PJRESUME"`, `"PJSTOP"`, `"PJABORT"`. +- **`ProcessComplete` / `AbortComplete`** — equipment-internal, + fire when the recipe runner or abort controller finishes. + +The transition table is loaded from +[`data/process_job_state.yaml`](../data/process_job_state.yaml). +Same spec-as-data pattern as E30 control state (chapter 13). + +### The E40 messages + +| S/F | Direction | Purpose | +|---------|-----------|-----------------------------------------------------------| +| S16F11 | H → E | PRJobCreate. Body carries PRJobID, MF, recipe spec, material list, PRProcessStart flag. | +| S16F12 | E → H | PRJobAck. PRJobAck byte: 0 = accepted, non-zero = errored. | +| S16F13 | H → E | PRJobDequeue. Host clears the PJ from equipment storage after observing ProcessComplete. | +| S16F14 | E → H | PRJobDequeueAck. | +| S16F5 | H → E | PRJobCommand. Body carries PRJobID + PRCMD string. | +| S16F6 | E → H | PRJobCommandAck. | +| S16F7 | H → E | PRJobMonitor. Host pulls current state for a PJ. | +| S16F8 | E → H | PRJobMonitorAck. Body carries PRJOBSTATE byte. | +| S16F9 | E → H | PRJobAlert. Equipment-initiated state-change notification. | + +`S16F9` is the *interesting* one: it's a W=0 unsolicited message +that fires on every state transition (configurable per-PJ via +`alert_enabled`). The body carries the new PRJOBSTATE so the +host can update its tracking without polling. + +Tested on the wire by +[`tests/test_wire_ceid_emission.cpp`](../tests/test_wire_ceid_emission.cpp) +("PJ Queued→SettingUp fires S16F9 PRJobAlert on the wire"). + +### The PJ store + +[`include/secsgem/gem/store/process_jobs.hpp`](../include/secsgem/gem/store/process_jobs.hpp) +houses one entry per PJ — id, MF, recipe spec, current state, +material list, alert_enabled bit. Persistent: a per-record file +journal lets the store survive equipment restarts (chapter +[36](36_persistence_validation_metrics.md)). + +Tests: [`tests/test_process_jobs.cpp`](../tests/test_process_jobs.cpp) +(21 cases — every transition, every wire message round-trip, +persistence). + +--- + +## E94 — Control Jobs + +### The CJ lifecycle + +Eight states, similar shape to PJ but distinct values (E94 doesn't +pin a wire byte for state; this project picks its own encoding): + +| Value | State | Meaning | +|-------|--------------------|------------------------------------------------------| +| 0 | `Queued` | Created; not yet promoted. | +| 1 | `Selected` | CJ has selected one of its PJs (the PJ is now `SettingUp`). | +| 2 | `WaitingForStart` | All material ready; awaiting CJSTART. | +| 3 | `Executing` | At least one PJ in flight. | +| 4 | `Paused` | Mid-execution pause. | +| 5 | `Completed` | All PJs done; awaiting deletion. | +| 6 | `Stopping` | Graceful abort in progress. | +| 7 | `Aborting` | Forceful abort in progress. | +| 255 | `NoState` | Sentinel. | + +Drivers: + +- **`Select`** — Queued → Selected (CJ promotes its first PJ). +- **`SetupComplete`** — Selected → WaitingForStart (PJ reached + WaitingForStart). +- **`Start` / `Pause` / `Resume` / `Stop` / `Abort`** — host-driven + via `S16F27 CJCMD` strings. CJCMD = `"CJSTART"`, `"CJPAUSE"`, + `"CJRESUME"`, `"CJSTOP"`, `"CJABORT"`. +- **`AllJobsComplete`** — internal: every PJ in the CJ reached + `ProcessComplete`. +- **`AbortComplete`** — internal: every PJ reached an aborted + state. + +Defined in +[`include/secsgem/gem/control_job_state.hpp`](../include/secsgem/gem/control_job_state.hpp); +transition table in +[`data/control_job_state.yaml`](../data/control_job_state.yaml). + +### The E94 messages + +| S/F | Direction | Purpose | +|--------|-----------|---------------------------------------------------------------| +| S14F9 | H → E | CreateControlJob. Body carries CJobID + ordered PRJobID list.| +| S14F10 | E → H | OBJACK reply. | +| S14F11 | H → E | DeleteControlJob. | +| S14F12 | E → H | OBJACK reply. | +| S16F27 | H → E | CJCommand. Body carries CJobID + CJCMD string. | +| S16F28 | E → H | HCACK reply. | + +The wire test in +[`apps/secs_conformance.cpp`](../apps/secs_conformance.cpp) drives +the full S14F9 → S16F27 (CJSTART) → S14F11 sequence as one +conformance check. + +### The CJ store + +[`include/secsgem/gem/store/control_jobs.hpp`](../include/secsgem/gem/store/control_jobs.hpp); +tests in [`tests/test_control_jobs.cpp`](../tests/test_control_jobs.cpp) +(9 cases). Also persistent. + +--- + +## How a PJ and its CJ cascade + +The interesting part isn't either FSM in isolation — it's how they +**cascade** during a typical batch run. + +``` +t=0 host S16F11 PRJobCreate (PJ-1) + → PJ-1 enters Queued + → S16F9 PRJobAlert (PJ-1 NoState → Queued, if alerts enabled) + +t=1 host S16F11 PRJobCreate (PJ-2) (similar) + +t=2 host S14F9 CreateControlJob (CJ-1, [PJ-1, PJ-2]) + → CJ-1 enters Queued + → equipment internally fires Select on PJ-1 (first in CJ list) + → PJ-1 enters SettingUp + S16F9 alert + → CJ-1 enters Selected + +t=3 equipment recipe runner: PJ-1 SetupComplete + → PJ-1 enters WaitingForStart + S16F9 alert + → CJ-1 enters WaitingForStart + +t=4 host S16F27 CJSTART (CJ-1) + → CJ-1 enters Executing + → CEID = ControlJobExecuting fires → S6F11 + → equipment fires Start on PJ-1 + → PJ-1 enters Processing + S16F9 alert + → CEID = ProcessStarted fires → S6F11 + +t=N equipment: PJ-1 recipe done + → PJ-1 enters ProcessComplete + S16F9 alert + → CEID = ProcessCompleted fires → S6F11 + → equipment fires Select on PJ-2 + → PJ-2 enters SettingUp ... + ... (same dance for PJ-2) + +t=M all PJs done + → CJ-1 fires AllJobsComplete + → CJ-1 enters Completed + → CEID = ControlJobCompleted fires → S6F11 + +t=M+1 host S16F13 PRJobDequeue (PJ-1) + host S16F13 PRJobDequeue (PJ-2) + host S14F11 DeleteControlJob (CJ-1) + → all three records removed +``` + +Notice three things: + +1. **CJ events drive PJ events.** CJSTART makes the CJ go to + Executing, which causes the equipment to fire Start on the + first PJ — the host doesn't send PJSTART explicitly. +2. **Every transition fires S16F9.** Hosts that subscribe to + S16F9 don't need to poll with S16F7; they get push + notification for every state change. +3. **CEIDs fire alongside FSM transitions.** `ControlJobExecuting`, + `ProcessStarted`, `ProcessCompleted`, `ControlJobCompleted` are + regular CEIDs (from `data/equipment.yaml`) that fire when the + FSMs transition. They drive S6F11 events bundled with whatever + reports the host has linked. + +End-to-end demonstration: +[`tests/test_live_gem300.cpp`](../tests/test_live_gem300.cpp) drives +the full cascade over a real loopback HSMS connection. + +--- + +## Why CJs exist as a separate layer + +Could the host just submit PJs one at a time and orchestrate the +batch itself? Yes — but: + +- **Ordering / dependencies** belong on the equipment side. The + CJ FSM ensures PJ-2 only starts after PJ-1 finishes, even if + the host network drops between them. +- **Abort semantics** are sharper. CJSTOP applies to *every PJ in + the CJ*, deterministically. Aborting a PJ at a time leaves + race windows. +- **Reporting** is unified. A CJ-level CEID summarises "this + batch is done"; without CJs the host has to track every PJ + completion individually. + +E94 is the layer that makes "run this batch of recipes safely" a +single command instead of an orchestration script. + +--- + +## Edge cases worth knowing + +- **PJ in Paused state when CJ goes Stopping.** The PJ has to + resume first (so the recipe runner can reach a safe stopping + point), then stop. The transition tables handle this. +- **Partial cancel.** Host can S16F5 PJABORT on a single PJ + inside a running CJ. The CJ continues with the remaining PJs. +- **CJ delete while PJs are still queued.** E94 §6: deleting a + CJ that owns Queued PJs cancels them — the equipment fires + `AbortComplete` on each. +- **PJ status byte on the wire is a raw `uint8_t`.** This is why + the enum values match the spec exactly — the encoder just + casts to byte. Don't reorder the enum. + +Every edge case has a test: +[`tests/test_process_jobs.cpp`](../tests/test_process_jobs.cpp) + +[`tests/test_control_jobs.cpp`](../tests/test_control_jobs.cpp) + +the CEID emission and live-scenario tests. + +--- + +## Where to go next + +You now know how a *job* is created, sequenced, executed, and torn +down. But before any of that can happen, the **carrier** holding +the material has to arrive at the tool — and the equipment has to +know it. + +Next: [→ 15 E87 — Carriers and load ports](15_e87_carriers.md) diff --git a/docs/15_e87_carriers.md b/docs/15_e87_carriers.md new file mode 100644 index 0000000..b65bb3f --- /dev/null +++ b/docs/15_e87_carriers.md @@ -0,0 +1,306 @@ +# 15 — E87: Carriers and load ports + +← [14 E40 + E94 — Process and control jobs](14_e40_e94_jobs.md) | [Back to index](00_index.md) | Next: [16 E90 + E157 — Substrate and module tracking](16_e90_e157_substrates_modules.md) → + +Before a wafer can be processed it has to physically arrive at the +tool, dock with the load port, expose its slot contents, get +verified, and be authorised by the host. That's six distinct +state transitions, each one tracked by SEMI **E87 — Carrier +Management** (2000). + +This chapter covers: + +- The carrier (FOUP) and the load port — what each *is* and what + state they each track. +- The three carrier state sub-machines: ID, Slot Map, Access. +- The three load-port state sub-machines: Transfer, Reservation, + Association. +- The E87 message catalog (S3F*). +- Where each piece is in code. + +--- + +## Vocabulary + +- **Carrier** — a physical container holding wafers. In a 300 mm + fab almost always a **FOUP** (Front-Opening Universal Pod, 25 + slots). In smaller fabs sometimes a SMIF pod or a cassette. +- **Load port** — the equipment-side dock where carriers physically + attach. A typical PVD tool has 2 load ports (1 input, 1 output); + a lithography stepper might have 4. +- **Slot map** — the equipment's reading of which of the carrier's + N slots contain wafers, expressed as N bytes. Slot states are + Empty, CorrectlyOccupied, DoubleSlotted, CrossSlotted, NotRead. +- **Carrier ID** — bar-coded string on the FOUP (e.g. "C-31415"). + Read by the equipment's bar-code reader on docking. + +--- + +## The three carrier state sub-machines + +E87 doesn't track "carrier state" as one variable — it tracks +three orthogonal aspects: + +### 1. ID Status + +How sure are we who this carrier is? + +```cpp +// include/secsgem/gem/carrier_state.hpp:23 +enum class CarrierIDStatus : uint8_t { + NotConfirmed = 0, + WaitingForHost = 1, + Confirmed = 2, + IDVerificationFailed = 3, +}; +``` + +Bar-code reader fires `ID_READ_OK` → NotConfirmed → Confirmed. +If the reader fails or returns gibberish, `ID_READ_FAIL` → +NotConfirmed → IDVerificationFailed. Some hosts insist on +verifying the ID themselves (look it up against their LMS); they +hold the carrier in WaitingForHost until they reply with +`HOST_ID_CONFIRMED`. + +### 2. Slot Map Status + +Have we read the carrier's contents? + +```cpp +// include/secsgem/gem/carrier_state.hpp:45 +enum class SlotMapStatus : uint8_t { + NotRead = 0, + WaitingForHost = 1, + Read = 2, + SlotMapVerificationFailed = 3, +}; +``` + +The mapper (an optical sensor reading wafer positions) runs after +ID confirmation. Result: a 25-byte vector (one byte per slot). +Hosts can validate the map against expectation (S3F19 Slot Map +Verify); a mismatch flips to `SlotMapVerificationFailed`. + +### 3. Access Status + +Is the carrier authorised for processing right now? + +```cpp +// include/secsgem/gem/carrier_state.hpp:64 +enum class CarrierAccessStatus : uint8_t { + NotAccessed = 0, + InAccess = 1, + CarrierComplete = 2, + CarrierStopped = 3, +}; +``` + +`InAccess` means the equipment is currently using slots from this +carrier. `CarrierComplete` means done; awaiting unloading. + +All three sub-machines progress **independently**. A carrier can +be `Confirmed` (ID) + `Read` (Map) + `NotAccessed` (Access) — and +that's the typical state after docking but before the host +authorises processing. + +`CarrierStateMachine` in +[`include/secsgem/gem/carrier_state.hpp`](../include/secsgem/gem/carrier_state.hpp) +composes the three; tests in +[`tests/test_carrier_state.cpp`](../tests/test_carrier_state.cpp) +(11 cases) and +[`tests/test_e87_wire_scenarios.cpp`](../tests/test_e87_wire_scenarios.cpp) +(4 wire scenarios). + +--- + +## The three load-port state sub-machines + +The load port has its own three sub-machines: + +### 1. Transfer State + +```cpp +// include/secsgem/gem/load_port_state.hpp:19 +enum class LoadPortTransferState : uint8_t { + OutOfService = 0, + ReadyToLoad = 1, + ReadyToUnload = 2, + InService = 3, +}; +``` + +Physical readiness — is the port mechanically ready to dock a new +FOUP, release the current one, or busy? + +### 2. Reservation Status + +```cpp +enum class LoadPortReservationStatus : uint8_t { + NotReserved = 0, + Reserved = 1, +}; +``` + +Has the host pre-reserved this port for an inbound carrier? +Reservation is how the host tells the AMHS "send the carrier to +this specific port." + +### 3. Association Status + +```cpp +enum class LoadPortAssociationStatus : uint8_t { + NotAssociated = 0, + Associated = 1, +}; +``` + +Is there a known Carrier object linked to this port? Becomes +`Associated` when a carrier docks and the ID is read. + +`LoadPortStateMachine` in +[`include/secsgem/gem/load_port_state.hpp`](../include/secsgem/gem/load_port_state.hpp). + +--- + +## Multi-port + multi-carrier + +A real fab tool runs **multiple load ports in parallel**. A +4-port cluster tool can have: + +- Port 1: carrier A in `Associated` + `InAccess` (processing) +- Port 2: carrier B in `Associated` + `CarrierComplete` (done, + awaiting unload) +- Port 3: AMHS robot docking carrier C (`InService`) +- Port 4: `OutOfService` (mechanical fault) + +The codebase models this as a **per-port store**: + +```cpp +// include/secsgem/gem/store/carriers.hpp +class CarrierStore { + // one CarrierStateMachine per Carrier ID + // one LoadPortStateMachine per PortID +}; +``` + +Tests for the parallel scenario: +[`tests/test_e87_wire_scenarios.cpp`](../tests/test_e87_wire_scenarios.cpp) +(4 multi-port scenarios — independence between ports asserted). + +--- + +## The E87 messages + +| S/F | Direction | Purpose | +|-------|-----------|--------------------------------------------------| +| S3F17 | H → E | CarrierAction. Body: CARRIERACTION + CARRIERID. | +| S3F18 | E → H | CAACK reply. | +| S3F19 | H → E | Slot Map Verify. Body: CARRIERID + expected slot states. | +| S3F20 | E → H | SMACK reply. | +| S3F25 | H → E | Carrier Transfer. Move a carrier between ports. | +| S3F26 | E → H | CAACK reply. | +| S3F27 | H → E | Cancel Carrier. Pre-arrival cancellation. | +| S3F28 | E → H | CAACK reply. | + +### CARRIERACTION strings (S3F17 body) + +The dominant E87 messages are S3F17 carrier-action commands. +CARRIERACTION is an ASCII string from a fixed E87 set: + +| String | Meaning | +|-------------------------|------------------------------------------------------| +| `ProceedWithCarrier` | Authorise processing. Triggers Access → InAccess. | +| `CancelCarrier` | Refuse the carrier; equipment doesn't process it. | +| `CancelCarrierAtPort` | Same but specifies port. | +| `BypassCarrier` | Process nothing from this carrier (audit slot map only). | +| `CarrierOut` | Mark `CarrierComplete`; AMHS will retrieve. | +| `CarrierReCID` | Re-read the carrier ID (e.g. bar code was iffy). | + +CAACK reply codes (S3F18, 1 byte): + +| Code | Meaning | +|------|--------------------------------------------------| +| 0 | Acknowledged. | +| 1 | Invalid command. | +| 2 | Cannot perform now. | +| 3 | Invalid carrier ID. | +| 4 | Invalid port ID. | +| 5 | Carrier ID unknown. | + +--- + +## A typical carrier flow + +``` +1. AMHS docks FOUP at port 1. +2. Equipment fires CarrierArrived event (CEID per equipment.yaml) + → S6F11 (host gets pinged). +3. Equipment reads bar code → CarrierIDStatus: NotConfirmed → Confirmed. +4. Equipment runs slot mapper → SlotMapStatus: NotRead → Read. +5. (Optional) Host sends S3F19 SlotMapVerify with expected contents + → equipment compares → SMACK = 0 (match) or 1 (mismatch). +6. Host sends S3F17 CARRIERACTION = "ProceedWithCarrier" + → CarrierAccessStatus: NotAccessed → InAccess. +7. Processing happens. Substrate state changes are tracked by E90 + (chapter 16). +8. All substrates done. Equipment fires CarrierComplete event. +9. Host sends S3F17 CARRIERACTION = "CarrierOut" + → CarrierAccessStatus → CarrierComplete. +10. AMHS retrieves FOUP from port 1. LoadPortAssociation goes back + to NotAssociated; carrier object can be deleted. +``` + +The slot-map-mismatch path in step 5 is tested by +[`tests/test_e87_wire_scenarios.cpp`](../tests/test_e87_wire_scenarios.cpp); +the happy path by +[`tests/test_carriers.cpp`](../tests/test_carriers.cpp) (6 cases). + +--- + +## Slot maps in detail + +A slot map is a **byte-vector** with one byte per carrier slot. +For a 25-slot FOUP, that's 25 bytes. Byte values: + +| Value | State | Meaning | +|-------|--------------------|------------------------------------------------| +| 0 | `Empty` | No wafer detected. | +| 1 | `CorrectlyOccupied`| One wafer in the correct vertical position. | +| 2 | `DoubleSlotted` | Two wafers in one slot — physical fault. | +| 3 | `CrossSlotted` | Wafer at an angle / wrong height. | +| 4 | `NotRead` | Sensor couldn't read this slot. | + +S3F19 (Slot Map Verify) carries the host's *expected* slot map. +The equipment compares against its read map; if they match, +SMACK = 0. Mismatches imply someone interfered with the carrier +(or the mapper is broken). + +The map is part of the carrier store and persists across restarts. + +--- + +## Persistence + +Like all GEM-300 stores, the carrier store is **persistent**. A +file per active carrier + a file per port lets the equipment +recover its full E87 state after a restart — including in-progress +carriers stranded mid-Access by a power loss. + +Tested by +[`tests/test_carrier_persistence.cpp`](../tests/test_carrier_persistence.cpp) +(6 cases — write, restart, replay, corrupted-file drop, removal). + +Per-store journal pattern is the same across E40, E87, E90, E94, +E116; chapter [36](36_persistence_validation_metrics.md) walks the +mechanism. + +--- + +## Where to go next + +You now know how a carrier arrives, gets authorised, and leaves. +But every wafer *inside* the carrier needs its own tracking — and +when wafers move into a process module, their state has to follow +them. That's **E90 and E157**. + +Next: [→ 16 E90 + E157 — Substrate and module tracking](16_e90_e157_substrates_modules.md) diff --git a/docs/16_e90_e157_substrates_modules.md b/docs/16_e90_e157_substrates_modules.md new file mode 100644 index 0000000..1d193a0 --- /dev/null +++ b/docs/16_e90_e157_substrates_modules.md @@ -0,0 +1,212 @@ +# 16 — E90 + E157: Substrate and module tracking + +← [15 E87 — Carriers and load ports](15_e87_carriers.md) | [Back to index](00_index.md) | Next: [17 E116 + E120 + E39 — Performance, CEM, objects](17_e116_e120_e39_objects.md) → + +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 + +```cpp +// 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 + +```cpp +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 + +```cpp +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`](../include/secsgem/gem/substrate_state.hpp) +defines `SubstrateStateMachine`, which composes the three. + +Store: +[`include/secsgem/gem/store/substrates.hpp`](../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`](../tests/test_substrates.cpp) (14 +cases — every axis, every event); persistence in +[`tests/test_substrate_persistence.cpp`](../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`](../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 25–50 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 + +```cpp +// 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`](../include/secsgem/gem/module_state.hpp) +defines `ModuleStateMachine`. + +Store: +[`include/secsgem/gem/store/modules.hpp`](../include/secsgem/gem/store/modules.hpp). + +Tests: +[`tests/test_modules.cpp`](../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. + +--- + +## 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](17_e116_e120_e39_objects.md) diff --git a/docs/17_e116_e120_e39_objects.md b/docs/17_e116_e120_e39_objects.md new file mode 100644 index 0000000..18f4952 --- /dev/null +++ b/docs/17_e116_e120_e39_objects.md @@ -0,0 +1,229 @@ +# 17 — E116 + E120 + E39: Performance, CEM, objects + +← [16 E90 + E157 — Substrate and module tracking](16_e90_e157_substrates_modules.md) | [Back to index](00_index.md) | Next: [18 E84 — Parallel I/O handoff](18_e84_parallel_io.md) → + +Three smaller GEM 300 standards in one chapter. Each is narrow in +scope but load-bearing for production fab operations. + +- **E116** — Equipment Performance Tracking. Time-buckets per + equipment state for OEE / utilisation reporting. +- **E120** — Common Equipment Model. A generic typed object + hierarchy the host can query. +- **E39** — Object Services. CRUD-style messages (`S14F*`) that + operate over E120 (and other) object types. + +--- + +## E116 — Equipment Performance Tracking + +### What it does + +In a fab, **equipment utilisation** is a primary KPI. Tools cost +$10–100M; idle minutes are visible on quarterly P&L statements. +E116 standardises how equipment reports *how much time it spent +in each state* so MES dashboards can compute OEE (Overall Equipment +Effectiveness) without per-vendor logic. + +### The states + +```cpp +// include/secsgem/gem/ept_state.hpp:22 +enum class EptState : uint8_t { + NonScheduledTime = 0, // not in the schedule (weekend, planned down) + UnscheduledDowntime = 1, // in schedule, but broken (alarm, fault) + ScheduledDowntime = 2, // in schedule, planned maintenance + Engineering = 3, // running engineering / qualification work + Standby = 4, // ready, awaiting material + Productive = 5, // actively processing +}; +``` + +These are the SEMI E116 §6.2 standard states. Per-state events: + +```cpp +enum class EptEvent { + Begin_NonScheduledTime, + Begin_UnscheduledDowntime, + Begin_ScheduledDowntime, + Begin_Engineering, + Begin_Standby, + Begin_Productive, +}; +``` + +### What the FSM records + +[`EptStateMachine`](../include/secsgem/gem/ept_state.hpp) is a +"what kind of time is this" classifier rather than a strict +lifecycle. Any state can transition to any other. What it +tracks: **how long was the equipment in each state**. + +The store accumulates time-buckets: + +```cpp +class EptStore { + // For each EptState, accumulated wall-clock duration. + std::array bucket_; + + // Current state + when it was entered (so the dwell so far is + // counted as part of the current bucket on read). +}; +``` + +A host querying "how much Productive time today?" gets the bucket +value for `Productive`, plus the dwell of the current state if +that state is Productive. + +### Wire + +E116 doesn't define its own S/F messages. Like E90, state changes +fire as **CEIDs** the host has subscribed to. + +Tests: +[`tests/test_ept.cpp`](../tests/test_ept.cpp) (7 cases — initial +state, transitions, bucket accumulation including current dwell, +reset, same-state no-op). + +### When EPT transitions happen + +EPT classification is *application logic*. The library doesn't +decide that processing a PJ = Productive — the EAP does, by +explicitly calling `EptStateMachine::on_event(Begin_Productive)` +when a PJ starts. Typical wiring: + +``` +PJ Processing → EPT Productive +PJ Paused → EPT Standby (or Engineering, depending on cause) +Alarm category 2 (equipment safety) → EPT UnscheduledDowntime +Maintenance recipe running → EPT ScheduledDowntime +``` + +The [`examples/pvd_tool/main.cpp`](../examples/pvd_tool/main.cpp) §5 +shows one concrete wiring; chapter +[41](41_integration_hardware_mes_production.md) discusses the +production patterns. + +--- + +## E120 — Common Equipment Model + +### What it does + +E120 defines a **generic typed object hierarchy** the equipment can +expose to the host. The motivation: every E30/GEM 300 standard +defines its own object type (Carrier, Substrate, ProcessJob, +ControlJob, Alarm, …), each with its own attributes. E120 says +"all of these are *objects* with the same hierarchical structure; +let's standardise how the host queries them." + +### The object types + +```cpp +// include/secsgem/gem/store/cem_objects.hpp:27 +enum class CemObjectType : uint8_t { + Equipment = 1, + IOProcessor = 2, + IODevice = 3, + SubsystemController = 4, + Subsystem = 5, + Module = 6, + // ... more +}; +``` + +Each object has: + +- A unique `OBJID` (ASCII string). +- A type from the enum above. +- A `parent_objid` (or empty for root — Equipment). +- A typed attribute bag. + +That builds the hierarchy: + +``` +Equipment "PVD-1" +├── IOProcessor "IOP-1" +│ ├── IODevice "Sensor-Pressure-A" +│ └── IODevice "Sensor-Temp-A" +└── SubsystemController "SubC-1" + ├── Subsystem "Vacuum" + │ └── Module "Pump-1" + └── Subsystem "Gas-Manifold" +``` + +The host can walk this tree, read attributes, and update its own +asset model. + +### Code + +Store: +[`include/secsgem/gem/store/cem_objects.hpp`](../include/secsgem/gem/store/cem_objects.hpp). +Tests: +[`tests/test_cem_objects.cpp`](../tests/test_cem_objects.cpp) (3 +cases — create, lookup, child enumeration). + +### Wire + +E120 itself doesn't define messages — it defines the *data model*. +The wire access is **E39 Object Services**. + +--- + +## E39 — Object Services + +### What it does + +E39 generalises "get attribute of an object" and "set attribute of +an object" into one message family — `S14F*` — that works across +**any object type** (E120 hierarchy, E40 process jobs, E94 control +jobs, E87 carriers, …). + +### The messages + +| S/F | Direction | Purpose | +|-------|-----------|----------------------------------------------------------| +| S14F1 | H → E | GetAttr. Body: object type + OBJID + attribute name list. | +| S14F2 | E → H | GetAttr reply. Body: attribute values + OBJACK byte. | +| S14F3 | H → E | SetAttr. Body: object type + OBJID + name/value pairs. | +| S14F4 | E → H | SetAttr reply. | + +OBJACK = 0 means accepted; non-zero means error. + +E39 is the **uniform API** for object introspection — same shape +of message whether the host is reading a Carrier attribute, an +Alarm attribute, or a Process Module attribute. + +### Code + +Handlers live in +[`include/secsgem/gem/host_command_registry.hpp`](../include/secsgem/gem/host_command_registry.hpp) +and the generated message catalog. + +Tests are bundled into +[`tests/test_cem_objects.cpp`](../tests/test_cem_objects.cpp) and +[`tests/test_messages.cpp`](../tests/test_messages.cpp) — the +`S14F1`/`S14F2` round-trip is exercised against multiple object +types. + +### Why E39 exists separately from E120 + +The split is the SEMI typical-shape: one standard defines the +*data model*, a separate standard defines the *wire access*. This +way E39 can extend to objects defined in other standards (E40 PJs, +E94 CJs, E87 carriers) without E120 having to know about them. + +In code, each object store registers itself with a generic +attribute-resolver; `S14F1` handlers look up the right resolver by +object type. + +--- + +## Where to go next + +You now know how the equipment reports *time* (E116), *structure* +(E120), and *attribute access* (E39). The next chapter is the +last GEM 300 standard with its own state machine — the **parallel +I/O handshake** that physically hands carriers between robot and +load port. + +Next: [→ 18 E84 — Parallel I/O handoff](18_e84_parallel_io.md) diff --git a/docs/18_e84_parallel_io.md b/docs/18_e84_parallel_io.md new file mode 100644 index 0000000..3007fed --- /dev/null +++ b/docs/18_e84_parallel_io.md @@ -0,0 +1,280 @@ +# 18 — E84: Parallel I/O handoff + +← [17 E116 + E120 + E39 — Performance, CEM, objects](17_e116_e120_e39_objects.md) | [Back to index](00_index.md) | Next: [19 E42 + E148 + S9 — Misc](19_e42_e148_s9_misc.md) → + +E84 is unusual in the GEM 300 suite: it's the only standard that's +**not SECS at all**. Not a wire format, not a message catalog — +ten *physical wires* between the AMHS robot and the load port, +asserted at CMOS voltage levels with strict timing. + +Why? Because dropping a $20 000 FOUP is catastrophic, and you +can't afford to coordinate the kinematics over TCP — too much +latency, too many failure modes. The handshake has to be +deterministic in hardware. + +This chapter: + +- The ten signal lines and what each one means. +- The handshake state machine. +- The three timing-critical timers (TA1, TA2, TA3). +- How the codebase models a physical-layer handshake as software + (and why it does). + +--- + +## The ten signals + +Each signal is one **single-bit boolean** asserted on a physical +wire. Four go from the equipment to the AMHS; six go from the +AMHS to the equipment: + +```cpp +// include/secsgem/gem/e84_state.hpp:28 +enum class E84Signal : uint8_t { + CS_0 = 0, // AMHS -> equip: carrier stage select 0 + CS_1 = 1, // AMHS -> equip: carrier stage select 1 + VALID = 2, // AMHS -> equip: handshake start + TR_REQ = 3, // AMHS -> equip: transfer request + BUSY = 4, // AMHS -> equip: transfer in progress + COMPT = 5, // AMHS -> equip: transfer complete + L_REQ = 6, // equip -> AMHS: load request (port ready to receive) + U_REQ = 7, // equip -> AMHS: unload request (port ready to release) + READY = 8, // equip -> AMHS: ready + ES = 9, // either: emergency stop +}; +``` + +- **CS_0 + CS_1**: two bits encoding which port the AMHS is + addressing (CS = Carrier Select). Tools with up to 4 ports can + be indexed by two bits. +- **VALID**: the AMHS asserts this when CS bits are stable — + "you can read me now." +- **TR_REQ**: AMHS is requesting a transfer. +- **BUSY**: AMHS is actively moving the carrier. Goes high when + the robot starts lowering / lifting. +- **COMPT**: AMHS has finished the kinematic operation. +- **L_REQ**: equipment is ready to *receive* a carrier. +- **U_REQ**: equipment is ready to *release* a carrier. +- **READY**: equipment kinematic interlocks are satisfied. +- **ES**: Emergency Stop. Either side can assert. If asserted, + every state machine on both sides goes to a safe state. + +Defined in +[`include/secsgem/gem/e84_state.hpp`](../include/secsgem/gem/e84_state.hpp). +Stored in `E84SignalSet` as a 10-bit bitmap. + +--- + +## The handshake state machine + +```cpp +// include/secsgem/gem/e84_state.hpp:63 +enum class E84State : uint8_t { + Idle = 0, // no signals + CarrierPresent = 1, // CS asserted; no VALID yet + ValidAsserted = 2, // CS + VALID; equipment hasn't ack'd + LoadReady = 3, // VALID + L_REQ; port ready to receive + UnloadReady = 4, // VALID + U_REQ; port ready to release + Transferring = 5, // BUSY asserted; transfer happening + Complete = 6, // COMPT asserted; AMHS done + EmergencyStop = 7, // ES asserted + HandoffFault = 8, // a timer expired +}; +``` + +The happy path for an **inbound load**: + +``` +Idle ─CS asserted─► CarrierPresent ─VALID asserted─► ValidAsserted + │ + (equipment + decides: yes, + I can take it) + │ + L_REQ asserted + ▼ + LoadReady + │ + TR_REQ asserted + BUSY asserted + ▼ + Transferring + │ + (robot lowers + carrier onto + port; takes a + few seconds) + │ + BUSY de-asserted + COMPT asserted + ▼ + Complete + │ + (signals all + drop back; CS + de-asserted) + ▼ + Idle +``` + +An **outbound unload** follows the same pattern but uses `U_REQ` +instead of `L_REQ`, and ends with the carrier moving *off* the +port. + +The FSM is **event-driven**: every transition is triggered by one +signal change, not by a clock tick. `E84StateMachine::on_signal_change()` +re-evaluates the bitmap and emits a state transition if one is due. + +Tests: +[`tests/test_e84.cpp`](../tests/test_e84.cpp) (6 cases — every +happy-path transition); +[`tests/test_e84_ports.cpp`](../tests/test_e84_ports.cpp) (5 cases +— per-port store). + +--- + +## The three TA timers + +These are why E84 matters more than "the AMHS lifts the carrier." +Without timer enforcement, a stuck signal could leave the +mechanical handoff frozen mid-motion — the robot holding the +carrier, neither side noticing the other has gone quiet. + +```cpp +struct E84Timeouts { + std::chrono::milliseconds ta1{0}; + std::chrono::milliseconds ta2{0}; + std::chrono::milliseconds ta3{0}; +}; +``` + +(Spec defaults are 2 s / 2 s / 60 s; tool builders tune per +port.) + +### TA1 + +Armed: on entering `ValidAsserted` (AMHS asserted VALID). +Cancelled: on entering `LoadReady` or `UnloadReady` (equipment +asserted L_REQ or U_REQ). + +Bounds: **how long may the equipment take to respond to VALID?** +If TA1 expires the AMHS doesn't know whether the equipment is busy, +broken, or asleep — fault. + +### TA2 + +Armed: on entering `LoadReady` or `UnloadReady`. +Cancelled: on entering `Transferring` (AMHS asserted BUSY). + +Bounds: **how long may the AMHS take to start moving once the +port is ready?** Prevents the equipment holding its port idle +forever waiting for an AMHS that's stuck. + +### TA3 + +Armed: on entering `Transferring`. +Cancelled: on entering `Complete`. + +Bounds: **how long may the actual transfer take?** If the robot +freezes mid-motion, TA3 catches it. + +### What happens on timeout + +The FSM transitions to `HandoffFault` with the relevant +`E84Fault` reason: + +```cpp +enum class E84Fault : uint8_t { + None = 0, + TA1Expired = 1, + TA2Expired = 2, + TA3Expired = 3, +}; +``` + +The equipment fires an alarm (configurable ALID per port), the +EAP brings up the operator panel, and someone has to physically +inspect. + +Tested by +[`tests/test_e84_timers.cpp`](../tests/test_e84_timers.cpp) (12 +cases — every timer armed/cancelled/expired path). + +--- + +## Why model a physical handshake in software + +The wires are real. The signals are CMOS-level on opto-isolated +24 V lines. But the software needs to: + +1. **Test the protocol logic without a real load port.** Spinning + up actual hardware for unit tests is impossible. +2. **Drive the timer enforcement.** Even if the wires are + physical, the timers TA1/TA2/TA3 are wall-clock and need a + software clock to track. +3. **Emit CEIDs alongside transitions.** When the port goes + `Transferring`, the equipment also wants to fire `CarrierIn` + over SECS-II — the same way E40/E87/E90 transitions do. +4. **Model multi-port concurrency.** A 4-port tool has four + independent E84 FSMs running in parallel; they have to be + modeled distinctly. + +The codebase ships **two implementations**: + +### Pure FSM (testable) + +[`E84StateMachine`](../include/secsgem/gem/e84_state.hpp) is the +IO-free FSM. Inputs: signal change events. Outputs: state +transitions + timer arm/cancel requests. No wall clock. + +This is what tests drive — they feed signal events in, expect +transitions out, and synthetically expire timers. + +### asio adapter (production) + +[`E84AsioTimers`](../include/secsgem/gem/e84_asio_timers.hpp) +wraps the FSM with real `asio::steady_timer`s. When the FSM +requests `arm(TA1, 2s)`, the adapter schedules a wall-clock timer; +when 2 s pass and nothing's cancelled it, the adapter feeds the +expiry event back into the FSM. + +This is what runs in production — connected to a GPIO driver +that pulses the actual wires. + +Tested by +[`tests/test_e84_asio_timers.cpp`](../tests/test_e84_asio_timers.cpp) +(4 cases — every timer fires on real wall clock). + +--- + +## How E84 connects to the rest of GEM + +E84 itself only manages the physical handoff. Once a carrier is +docked, *SECS messages* take over: + +1. E84 reaches `Complete` → equipment fires CEID `CarrierArrived` + (configured in `data/equipment.yaml`). +2. CEID `CarrierArrived` fires → S6F11 (host informed). +3. Host sees S6F11 → looks up carrier ID → optionally sends + S3F19 SlotMapVerify (E87). +4. Host sends S3F17 `ProceedWithCarrier` → CarrierAccess goes + InAccess (E87). +5. Processing happens (E40 + E90 + E157). +6. All wafers done → equipment fires CEID `CarrierComplete`. +7. Host sends S3F17 `CarrierOut`. +8. AMHS comes back; E84 runs in reverse to unload. + +E84 is the **bookend** at both ends of the carrier flow. Without +it, the carrier never docks and never undocks; without the SECS +messages after step 1, nothing knows the carrier arrived. + +--- + +## Where to go next + +You now know every state-machine-bearing standard in the GEM 300 +suite. One more chapter wraps up the remaining narrow ones — +formatted process programs, distributed time sync, and the +exception recovery streams. + +Next: [→ 19 E42 + E148 + S9 — Misc](19_e42_e148_s9_misc.md) diff --git a/docs/19_e42_e148_s9_misc.md b/docs/19_e42_e148_s9_misc.md new file mode 100644 index 0000000..d0b0d1f --- /dev/null +++ b/docs/19_e42_e148_s9_misc.md @@ -0,0 +1,264 @@ +# 19 — E42 + E148 + S9 + exception recovery + +← [18 E84 — Parallel I/O handoff](18_e84_parallel_io.md) | [Back to index](00_index.md) | Next: [30 Repository tour](30_repository_tour.md) → + +Three remaining standards-shaped concerns to round out Part 2: + +- **E42** — Formatted (enhanced) Process Programs. +- **E148** — Time synchronization. +- **S5F9–S5F18** — Exception recovery (E5 §13 + GEM Additional). + +Each is narrow enough that a half-chapter would do. Together they +round out the GEM 300 picture. + +--- + +## E42 — Formatted Process Programs + +### What it is + +E30's Process Program Management (chapter 13) covers **unformatted** +recipes: the PPBODY is opaque bytes that only the equipment knows +how to parse. E42 adds **formatted** PPs — the recipe has a typed +SECS-II structure the host can introspect. + +In practice, formatted recipes are a fab-internal standard rather +than a SEMI-defined schema. E42 just gives the wire shape; the +fab agrees what the structure means. + +### The messages + +| S/F | Direction | Purpose | +|-------|-----------|---------------------------------------------------------------| +| S7F23 | H → E | Formatted PP Send. Body: PPID + typed SECS-II body. | +| S7F24 | E → H | ACKC7 reply. | +| S7F25 | H → E | Formatted PP Request. Body: PPID. | +| S7F26 | E → H | Formatted PP Send (back). Body: PPID + typed body. | + +Compare to unformatted S7F3 / S7F5: same direction pattern, just a +typed body instead of opaque bytes. + +### Implementation + +[`RecipeStore`](../include/secsgem/gem/store/recipes.hpp) carries +**both** views per recipe: an unformatted PPBODY (opaque bytes) +and an optional formatted body (a `secs2::Item` tree). S7F3 sends +the unformatted; S7F23 sends the formatted; both are stored side +by side and the host can request either via S7F5 (unformatted) or +S7F25 (formatted). + +Tests: +[`tests/test_e42_formatted_pp.cpp`](../tests/test_e42_formatted_pp.cpp) +(6 cases — send formatted, request back, round-trip integrity, +ACKC7 error paths). + +### Why both? + +Some MES — and some equipment — only speak unformatted PPs. +Coexistence lets a vendor ship one EAP that handles both. +COMPLIANCE §4j has the audit detail. + +--- + +## E148 — Time synchronization + +### What it does + +In a multi-tool fab, the host and the equipment need a **common +notion of time** for timestamp correlation. If tool A logs an +alarm at 14:32:01 and tool B logs a related alarm at 14:31:58, did +B precede A or did the clocks drift? + +E148 defines the wire mechanism for keeping equipment clocks in +sync with a host-authoritative time source — typically NTP behind +the scenes — and lets equipment **report clock quality** so the +host knows how much to trust the timestamps coming off the tool. + +### The messages + +E148 doesn't add new streams; it specialises the existing E30 +clock messages: + +| S/F | Direction | Purpose | +|-------|-----------|----------------------------------------------------------| +| S2F17 | H → E | Read clock. | +| S2F18 | E → H | Reply with current time string. | +| S2F31 | H → E | Set clock to specified time string. | +| S2F32 | E → H | TIACK reply. | + +The time string is **16 ASCII chars `YYYYMMDDhhmmsscc`** (E148 +extended form, including hundredths). 14-char `YYYYMMDDhhmmss` +(without hundredths) is the older E30 form; the codebase **accepts +both on set** but emits 16 chars on read by default. See +[`docs/COMPLIANCE.md`](COMPLIANCE.md) §4g. + +### Clock store + +[`include/secsgem/gem/store/clock.hpp`](../include/secsgem/gem/store/clock.hpp) +holds the wall-clock plus a **drift / quality** indicator: + +- `Drift_ms`: cumulative drift since last set. +- `Quality`: enum from {Authoritative, GoodNTP, FreeRunning, + Unreliable}. + +A host can read both via E120/E39 attribute access (chapter 17) or +via DVID exposures (the EAP wires them). + +### Why this matters + +Without clock sync, **alarm root-cause analysis is impossible**. +SPC charts get the X-axis wrong. Yield correlations across tools +fall apart. Most modern fabs run NTP on every tool's +control-plane host; E148 is the mechanism for *reporting* clock +state, not for synchronising it (that's NTP's job). + +--- + +## Exception recovery — S5F9–S5F18 + +### What it adds beyond base alarms + +E5 §13 + E30 Alarm Management (covered in chapter 13) handles +alarms as **set/clear** events: an alarm goes active, the +equipment fires S5F1; later it clears, equipment fires another +S5F1. Simple. + +But some alarms aren't simple to clear. A vacuum leak that +required a chamber vent + manual seal replacement can't just +"go away" — the equipment has to run a recovery procedure with +the host's involvement. **S5F9–S5F18** is the **exception +recovery** family that handles that. + +### The exception lifecycle + +Defined in +[`include/secsgem/gem/exception_state.hpp`](../include/secsgem/gem/exception_state.hpp): + +```cpp +enum class ExceptionState : uint8_t { + Posted = 0, // S5F9 sent; awaiting host action + Recovering = 1, // S5F13 accepted; recovery in progress + RecoverFailed = 2, // S5F15 reported failure; retry possible + Cleared = 3, // resolved; terminal +}; +``` + +Events: `Created` (NoState → Posted), `Recover` (host's S5F13), +`RecoveryComplete`, `RecoveryFailed`, `RecoveryAbort` (host's +S5F17), `Clear`. + +### The messages + +| S/F | Direction | Purpose | +|-------|-----------|--------------------------------------------------------------| +| S5F9 | E → H | Exception Post. Equipment-initiated. Body: EXID + EXTYPE + EXMESSAGE + recovery-method list. | +| S5F10 | H → E | Ack. | +| S5F11 | E → H | Exception Clear. Equipment-initiated when condition resolves. | +| S5F12 | H → E | Ack. | +| S5F13 | H → E | Exception Recover. Body: EXID + which recovery method to attempt. | +| S5F14 | E → H | Recovery progress. | +| S5F15 | E → H | Recovery Complete (or Failed). | +| S5F16 | H → E | Ack. | +| S5F17 | H → E | Exception Recover Abort. Cancel an in-progress recovery. | +| S5F18 | E → H | Recovery Aborted reply. | + +The flow: + +``` +1. Vacuum leak detected. EAP calls exceptions.post(EXID=42, recovery=["vent","seal","pump-down"]). + → ExceptionState: NoState → Posted. + → S5F9 fires. +2. Host sees S5F9 → operator decides to attempt recovery → host sends S5F13(EXID=42, method="vent"). + → ExceptionState: Posted → Recovering. +3. Recovery in progress. EAP fires S5F14 periodically with progress. +4. EAP completes the venting step. Fires recover_complete event. + → ExceptionState: Recovering → Cleared. + → S5F15 fires. +5. Host acknowledges (S5F16). EAP fires S5F11 to confirm the + underlying condition is gone. +``` + +Or, the abort path: + +``` +3'. Operator decides recovery isn't working → host sends S5F17. +4'. ExceptionState: Recovering → Posted. +5'. EAP can be re-instructed via another S5F13 with a different + method, or the condition can clear autonomously (Clear event + → Cleared state). +``` + +### Code + +State machine: +[`include/secsgem/gem/exception_state.hpp`](../include/secsgem/gem/exception_state.hpp). +Store: +[`include/secsgem/gem/store/exceptions.hpp`](../include/secsgem/gem/store/exceptions.hpp) +— persistent, so an exception in flight survives a power cycle. +Tests: +[`tests/test_exceptions.cpp`](../tests/test_exceptions.cpp) (11 +cases) + persistence in +[`tests/test_exception_persistence.cpp`](../tests/test_exception_persistence.cpp) +(5 cases). + +### Why this is its own family + +Two reasons: + +1. **State persistence.** Alarms come and go in seconds; exception + recovery can span hours and a few power cycles. The store + journal lets the equipment remember "we were halfway through + recovery method 2 of EXID=42" across restarts. +2. **Multi-step coordination.** Each step (`S5F13` → `S5F14` → + `S5F15`) is a host-supervised transaction. Base alarms can't + express "host, here are three recovery options, pick one." + +Exception recovery is an Additional GEM capability — not every MES +asks for it — but the codebase implements it because it's +upstream-absent in `secsgem-py` (see [docs/COMPLIANCE.md](COMPLIANCE.md) +§4k) and because the persistent state machine is a nice example of +the spec-as-data pattern applied to a less-trivial FSM. + +--- + +## The auto-S9 family (revisited) + +We covered the S9 wire-error replies in chapter 11. Worth +re-listing here because S9 is part of the **error/exception layer** +even though it's transport-level rather than application-level: + +| Function | Trigger | +|----------|----------------------------------------------------------| +| S9F1 | Unrecognized Device ID | +| S9F3 | Unrecognized Stream | +| S9F5 | Unrecognized Function | +| S9F7 | Illegal Data (body failed to decode) | +| S9F9 | Transaction Timer Timeout (T3 expired) | +| S9F11 | Data Too Long (body exceeded configured cap) | +| S9F13 | Conversation Timer Timeout (equipment-internal) | + +Implementation: +[`hsms::Connection::emit_s9`](../include/secsgem/hsms/connection.hpp) +called from the connection's framing and routing paths. Tested +across [`tests/test_hsms_s9.cpp`](../tests/test_hsms_s9.cpp) and +[`tests/test_s9_fallback.cpp`](../tests/test_s9_fallback.cpp). + +Difference from S5F9: S9 is **transport-level** (the bytes +themselves were wrong); S5F9 is **application-level** (the +equipment can't continue normal operation). + +--- + +## End of Part 2 + +You now know every SECS/GEM and GEM 300 standard that this +codebase implements. Twelve standards across nine chapters, each +one mapped to its state machine, its messages, its store, and the +tests that hold it down. + +Part 3 starts. We turn from "what the spec says" to "how this +codebase implements it" — repository tour, codegen, the data model +structure, transport internals, state-machine composition, +persistence mechanics. + +Next: [→ 30 Repository tour](30_repository_tour.md)