docs: chapters 14–19 — GEM 300 standards (Part 2 complete)
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 <noreply@anthropic.com>
This commit is contained in:
@@ -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)
|
||||
Reference in New Issue
Block a user