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,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)
|
||||
Reference in New Issue
Block a user