Files
secs-gem/docs/14_e40_e94_jobs.md
raphael 40df3067a4 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>
2026-06-09 20:14:42 +02:00

13 KiB

14 — E40 + E94: Process jobs and Control jobs

13 E30 — GEM | Back to index | Next: 15 E87 — Carriers and load ports

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.

Drivers of the FSM (ProcessJobEvent):

  • Created — synthetic observer signal when the store first records a PJ. Doesn't appear in the transition table.
  • SelectQueued → 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. 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 ("PJ Queued→SettingUp fires S16F9 PRJobAlert on the wire").

The PJ store

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).

Tests: 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; transition table in 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 drives the full S14F9 → S16F27 (CJSTART) → S14F11 sequence as one conformance check.

The CJ store

include/secsgem/gem/store/control_jobs.hpp; tests in 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 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_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