40df3067a4
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>
297 lines
13 KiB
Markdown
297 lines
13 KiB
Markdown
# 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)
|