docs: chapters 02 + 03 of the guided tour (Part 1 complete)

02 — The cast of characters: equipment, EAP, MES, fab planner, AMHS,
operator.  Who initiates which conversation, why the equipment is
the passive side of HSMS by convention, how the AMHS handshake is
out-of-band relative to SECS.  Cross-references the relevant
namespace and test files for each actor.

03 — Vocabulary + a wafer's journey: follows one 300 mm wafer
end-to-end through a fab and labels every SECS message and acronym
that fires.  Introduces SVID / DVID / ECID / CEID / RPTID / ALID /
PPID / MDLN / SOFTREV / HCACK / ALCD / OFLACK / CAACK / SMACK / etc.
in context rather than as a list.  Includes one-screen reference
tables for the remaining acknowledge codes, T-timers in all four
contexts (HSMS / SECS-I / E84 / E30 communication state), and a
stream-by-stream summary.

Part 1 (Foundations) of the guided tour is now complete — a reader
who reads chapters 01–03 can describe the protocol stack, identify
the actors, and recognise every acronym they'll meet in Part 2.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-09 19:35:43 +02:00
parent bc54de7711
commit 60fa164626
12 changed files with 1009 additions and 0 deletions
+417
View File
@@ -0,0 +1,417 @@
# 02 — The cast of characters
← [01 What is SECS/GEM?](01_what_is_secs_gem.md) | [Back to index](00_index.md) | Next: [03 Vocabulary + a wafer's journey](03_vocabulary_and_a_wafers_journey.md) →
Chapter [01](01_what_is_secs_gem.md) explained that SECS/GEM is the
protocol a fab uses to make ~100 tools talk to a central MES. That
description hid a lot of structure. In reality, there are at least
**six distinct actors** in a typical fab automation stack — six
roles, each implemented by different software (often by different
vendors), each with its own concerns.
This chapter introduces them all, draws who-talks-to-whom, and
locates each one in this codebase. After this you'll be able to read
any SECS conversation and know which actor is initiating, which is
responding, and why.
---
## The six actors
```
┌──────────────────┐
│ Fab planner │ "make 100 wafers
│ (MES upper) │ of recipe R by
└────────┬─────────┘ Friday"
recipes, lot │ yields, KPIs,
assignments, │ alarms, status
process programs │
┌──────────────────┐
│ MES (the host) │ per-step orchestration
└────────┬─────────┘
SECS/GEM │ SECS/GEM
S2F41 RCMD, │ S6F11 events,
S7F3 PP send, │ S5F1 alarms,
S2F33 reports, │ S1F4 status
… │ …
┌──────────────────┐
│ EAP / equipment automation program │
│ (vendor application layer) │
├──────────────────────────────────────────┤
│ THIS CODEBASE — the SECS/GEM runtime │
│ secsgem::gem / secsgem::hsms / … │
└────────┬─────────────────────────────────┘
PLC / sensor / recipe-engine APIs (tool-specific)
┌──────────────────┐
│ Equipment │ the physical tool:
│ (the tool) │ chambers, robots,
└────────┬─────────┘ sensors, recipes
E84 8-line │ (carrier moves, no SECS bytes)
parallel I/O │
┌──────────────────┐
│ AMHS │ robot rails / OHT
│ (the carriers) │ that move FOUPs
└──────────────────┘
←───── Operator ──────→ panel buttons,
recipe overrides,
Online/Offline/Local/Remote
```
Read the diagram top-down: a fab planner schedules work, the MES
dispatches it tool by tool, each tool's EAP receives commands over
SECS/GEM, the EAP drives the actual hardware, the AMHS robots feed
carriers in and out. An operator can intervene at any layer.
Each actor has a section below.
---
## 1. Equipment — the tool itself
**What it is.** A physical processing tool — a chemical-vapor
deposition (CVD) chamber, a plasma etcher, a wafer prober, a
photolithography stepper, an ion implanter, an inspection
microscope. Anywhere from one chamber the size of a microwave to a
full lithography cluster the size of a small bus.
**What it does in SECS/GEM terms.**
- **Reports state** — its current control state (Equipment Offline,
Online Remote, …), its processing state (IDLE, EXECUTING, …), its
carrier slots, its current recipe.
- **Emits events** — when something happens worth recording
(processing started, wafer processed, alarm raised, recipe
completed), it fires an `S6F11` to the host.
- **Accepts commands** — START, STOP, ABORT, PAUSE, CHANGE-RECIPE,
CARRIER-PROCEED, etc., delivered as `S2F41` Host Commands.
- **Stores its data dictionary** — every Status Variable (SVID),
Equipment Constant (ECID), Data Variable (DVID), Collection Event
(CEID), Alarm (ALID), and Process Program (PPID) it supports.
- **Manages its own physical safety** — it can refuse a host command
if the requested action would damage hardware, and it can raise
alarms autonomously.
**In SECS/GEM, the equipment is almost always the "passive" side of
the connection** — it binds a TCP port and waits for the host to
connect, rather than the other way around. This codebase reflects
that: `apps/secs_server.cpp` is the equipment, and it listens.
**Where it lives in this codebase.**
- The equipment role's main binary: [`apps/secs_server.cpp`](../apps/secs_server.cpp).
- The data dictionary: [`include/secsgem/gem/data_model.hpp`](../include/secsgem/gem/data_model.hpp)
defines `EquipmentDataModel`, which composes every per-domain
store (SVIDs, ECIDs, CEIDs, alarms, carriers, substrates, recipes,
spool, …).
- A worked example with sensor simulation, recipe runner, and alarm
monitoring: [`examples/pvd_tool/main.cpp`](../examples/pvd_tool/main.cpp).
- Tests covering equipment behaviour: [`tests/test_data_model.cpp`](../tests/test_data_model.cpp),
[`tests/test_control_state.cpp`](../tests/test_control_state.cpp),
[`tests/test_host_handler.cpp`](../tests/test_host_handler.cpp).
---
## 2. EAP — the equipment automation program
**What it is.** The vendor-written software layer that sits on top
of the SECS/GEM runtime and *makes the tool actually do things*.
The EAP is the glue between:
- The SECS/GEM library (this codebase),
- The tool's PLCs / sensors / recipe engine / robot controllers,
- The tool vendor's domain logic.
Every tool vendor ships their own EAP. Two CVD tools from different
vendors both speak GEM, but their EAPs are entirely different
codebases doing entirely different things internally.
**Why it's a separate role.** The SECS/GEM standards spell out
*what* messages mean — "S2F41 with RCMD=START must initiate
processing on the currently loaded recipe." They don't spell out
*how* a specific CVD tool initiates processing on its specific
hardware. The EAP is the layer that resolves that.
In particular:
- When `S2F41 RCMD=START` arrives, the EAP decides whether the tool
is in a state to start (chamber pressure low enough? robot at
home position? recipe loaded?), and if so, calls the tool's
proprietary recipe engine to begin the cycle.
- When a sensor reads a temperature change, the EAP decides whether
to update an SVID, fire a CEID, or raise an alarm — and the
per-tool rules for that aren't in any SEMI standard.
- When a `S7F3` arrives with a new recipe payload, the EAP decides
how to validate the recipe against the tool's actual hardware
capabilities.
**Where it lives in this codebase.**
This codebase provides the SECS/GEM runtime; the EAP is what a
customer writes on top of it. We ship two reference EAPs:
- [`apps/secs_server.cpp`](../apps/secs_server.cpp) — the demo
server. Wires every Router handler the demo flow needs; uses
static YAML data and doesn't simulate any sensors. Useful as a
starting fork.
- [`examples/pvd_tool/main.cpp`](../examples/pvd_tool/main.cpp) — a
fictional PVD tool that adds a sensor simulator, a recipe runner,
an alarm threshold monitor, EPT state cycling, and Prometheus
metrics. This is the closest thing to "what a real EAP looks
like" that we ship. See [`examples/pvd_tool/README.md`](../examples/pvd_tool/README.md)
for the section-by-section walk.
The integration tutorial — how to *write* an EAP for a real tool —
is [`INTEGRATION.md`](INTEGRATION.md). Chapter
[41](41_integration_hardware_mes_production.md) in this series covers
the same material with cross-references back to the standards.
---
## 3. MES — the host
**What it is.** The **Manufacturing Execution System**. A
fab-wide server (or cluster) that orchestrates production across
every tool, manages lots and recipes, collects yield and statistical
process control (SPC) data, and provides the operator UI for the
production floor.
Commercial MES vendors you'll meet: Applied Materials **E3**, Camstar
**InSite**, Wonderware **MES**, Aegis **FactoryWorks**, Inficon
**FabGuard**, Critical Manufacturing **MES**, and many in-house
custom builds especially at the largest fabs.
**What it does in SECS/GEM terms.**
- **Connects** to each tool's equipment process. In SECS/GEM
language, the MES is the **active** side of the HSMS connection
(it initiates the TCP connect and sends `Select.req`).
- **Establishes communications** — sends `S1F13` to which the
equipment replies `S1F14(COMMACK=Accept)`.
- **Identifies the tool** — sends `S1F1` (Are You There), reads
back the `MDLN` (model name) and `SOFTREV` (software revision)
in `S1F2`.
- **Reads the data dictionary** — `S1F11` for the SVID namelist,
`S2F29` for the ECID namelist, `S1F23` for the CEID namelist,
`S5F5` for the alarm directory, `S7F19` for the recipe list.
- **Configures event reports** — `S2F33` defines a report,
`S2F35` links it to a Collection Event, `S2F37` enables it. This
is how the MES tells the tool "when CEID 300 fires, send me the
values of SVIDs 1 and 2 along with it."
- **Issues remote commands** — `S2F41 RCMD=START`, `S2F41
RCMD=PAUSE`, `S2F41 RCMD=ABORT`, etc.
- **Manages recipes** — `S7F3` to send a recipe, `S7F19` to list,
`S7F17` to delete, `S7F5` to read one back.
- **Orchestrates process and control jobs** — `S16F11` to create
a Process Job, `S14F9` to wrap it in a Control Job, `S16F27`
CJSTART to begin execution.
- **Receives alarms and events** — `S5F1` for alarm set/clear,
`S6F11` for collection events. Acknowledges with `S5F2` and
`S6F12` respectively.
- **Sets and reads the equipment's clock** — `S2F17`/`S2F18` to
read, `S2F31`/`S2F32` to set.
**Where it lives in this codebase.**
We don't *implement* an MES — that's a separate, much larger product
category. We implement the host *side* of SECS/GEM so the codebase
can drive equipment too, mainly for testing.
- [`apps/secs_client.cpp`](../apps/secs_client.cpp) — the active
host that drives the demo server through ~24 transactions.
- [`apps/secs_conformance.cpp`](../apps/secs_conformance.cpp) — the
host-driven conformance harness that runs the 47 wire-level checks.
- [`include/secsgem/gem/host_handler.hpp`](../include/secsgem/gem/host_handler.hpp)
+ [`src/gem/host_handler.cpp`](../src/gem/host_handler.cpp) —
symmetric handler module so the host side can decode equipment
replies and act on equipment-initiated S5F1 / S6F11.
- [`interop/host_vs_cpp_server.py`](../interop/host_vs_cpp_server.py)
— the secsgem-py active host driving our C++ passive server.
For integrating against a **commercial** MES,
[`MES_INTEROP.md`](MES_INTEROP.md) is the day-1 punch list.
---
## 4. Fab planner / MES upper layer
**What it is.** The layer *above* the MES. Goes by many names:
**Advanced Planning and Scheduling (APS)**, **Fab scheduler**,
**Dispatcher**, **MES upper**. Big fabs separate this from the
operational MES; smaller ones bundle it in.
**What it does.** Decides which lot runs on which tool, in what
order, against what recipe, by what deadline. This is fab-wide
optimisation across hundreds of in-flight lots and dozens of routes.
**SECS/GEM contact:** none directly. The planner talks to the MES
via REST / SQL / a message queue / a proprietary API. The MES
translates planner decisions into SECS commands.
**Where it lives in this codebase.** Not implemented; out of scope.
Mentioned here so the reader knows where the recipes and lot
assignments ultimately come from, but no codebase artifact
corresponds to this layer.
---
## 5. AMHS — Automated Material Handling System
**What it is.** The robot-rail network and overhead hoist transport
(**OHT**) system that physically moves carriers (FOUPs holding ~25
wafers each) between tools. In a modern 300 mm fab the AMHS is
*always* moving carriers between tools 24/7; humans never touch a
substrate.
**What it does in SECS/GEM terms.**
- The AMHS itself **doesn't speak SECS/GEM** — it has its own
control plane talking to a Material Control System (MCS) which is
conceptually peer to the MES.
- But every time a carrier *arrives at* or *departs from* an
equipment's load port, the AMHS-side robot and the equipment-side
load port **handshake over 8 parallel I/O lines** defined by
**E84**. This is a physical-layer handshake (CMOS-level voltages
on real wires) with strict timing — TA1, TA2, TA3 timers — to
make sure a $20 000 FOUP doesn't get dropped because both sides
thought the other one was holding it.
- Once the carrier is physically docked, the equipment fires a
`S6F11(CarrierArrived)` event to the MES and the MES sends back a
`S3F17(CarrierAction=ProceedWithCarrier)` to authorise processing.
**Where it lives in this codebase.**
- The E84 handshake state machine: [`include/secsgem/gem/e84.hpp`](../include/secsgem/gem/e84.hpp)
+ [`src/gem/e84.cpp`](../src/gem/e84.cpp).
- The TA1/TA2/TA3 timer wiring: [`include/secsgem/gem/e84_timers.hpp`](../include/secsgem/gem/e84_timers.hpp),
[`include/secsgem/gem/e84_asio_timers.hpp`](../include/secsgem/gem/e84_asio_timers.hpp).
- The per-port store: `e84_ports.hpp` (see [`include/secsgem/gem/e84_ports.hpp`](../include/secsgem/gem/e84_ports.hpp)).
- Tests covering the timing rules: [`tests/test_e84.cpp`](../tests/test_e84.cpp),
[`tests/test_e84_timers.cpp`](../tests/test_e84_timers.cpp),
[`tests/test_e84_asio_timers.cpp`](../tests/test_e84_asio_timers.cpp),
[`tests/test_e84_ports.cpp`](../tests/test_e84_ports.cpp).
Chapter [18](18_e84_parallel_io.md) covers E84 in full.
---
## 6. Operator — the human
**What it is.** The fab technician at the tool's local panel. Their
job is to handle anything the automation can't: load a non-AMHS
carrier, clear a jammed wafer, run a maintenance recipe, respond to
an alarm the MES can't auto-clear.
**What they do in SECS/GEM terms.**
- **Mode switch.** The operator can push the equipment between
control states: `EquipmentOffline`, `OnlineLocal` (commands
accepted only from the local panel), `OnlineRemote` (commands
accepted from the MES). This is E30 §6.2.
- **Override.** An operator can override an MES command (refuse to
start, force-clear an alarm, manually unload a carrier). In
SECS/GEM terms this is reflected by control-state transitions:
`OnlineRemote` → `OnlineLocal` means "operator has taken control."
- **Local alarm acknowledgement.** Some alarms can be cleared at
the panel without the MES being involved; the equipment then
emits an `S5F1` with the cleared bit so the MES catches up.
**Where it lives in this codebase.**
- The control state machine: [`include/secsgem/gem/control_state.hpp`](../include/secsgem/gem/control_state.hpp)
+ [`src/gem/control_state.cpp`](../src/gem/control_state.cpp).
- The transition table loaded from YAML: [`data/control_state.yaml`](../data/control_state.yaml).
- The operator-initiated transition handlers:
`ControlStateMachine::operator_online`, `::operator_offline`,
`::operator_local`, `::operator_remote` in the same header.
- Tests: [`tests/test_control_state.cpp`](../tests/test_control_state.cpp).
Chapter [13](13_e30_gem.md) walks through control state in detail.
---
## Who talks to whom
A short reference table. "Init." marks who initiates the
conversation; "Channel" marks the protocol layer.
| Pair | Init. | Channel | Examples |
|----------------------------|------------|---------------------------------------------|-----------------------------------------------------------|
| Planner ↔ MES | Planner | REST / SQL / queue (out of scope) | "run lot L on tool T with recipe R" |
| MES ↔ EAP | MES | HSMS-SS (one TCP socket, equipment passive) | `S1F1`, `S2F41`, `S6F11`, `S5F1`, … |
| MES ↔ EAP (multi-MES) | MES | HSMS-GS (one TCP socket, multiple sessions) | Same messages, demuxed by session_id |
| EAP ↔ Equipment | Either | PLC / sensor APIs / recipe engine (tool-specific) | Out of scope of SECS/GEM |
| AMHS ↔ Load port | AMHS | E84 8-line parallel I/O | VALID/CS_0/CS_1/TR_REQ/READY/BUSY/COMPT/CONT/L_REQ/U_REQ/ES |
| MES ↔ EAP (carrier flow) | Equipment | HSMS | `S6F11(CarrierArrived)`, `S3F17(ProceedWithCarrier)` |
| Operator ↔ Equipment | Operator | Local panel | Online/Offline buttons, alarm acks |
The four interesting things in this table:
1. **The MES is the active side, the equipment is the passive side.**
Always. Equipment binds the port; MES connects to it. Some MES
want this reversed and will negotiate, but the GEM default is
equipment-passive.
2. **One TCP socket per (MES, equipment) pair.** HSMS-SS doesn't
multiplex; one connection serves one conversation. HSMS-GS adds
session multiplexing on top.
3. **Equipment-initiated traffic exists.** `S6F11` events and
`S5F1` alarms fire from equipment to MES *autonomously*, not
in reply to a host command. An EAP that never emits unsolicited
traffic is broken.
4. **The AMHS handshake is out-of-band relative to SECS/GEM.**
E84's 8 parallel I/O lines are real wires with real voltages;
the SECS messages that follow (`S6F11`, `S3F17`) are just the
*bookkeeping* around a handoff that already happened in
hardware.
---
## A small mental check
If you've internalised the chapter, you should be able to answer:
1. When the MES sends `S1F13`, who initiates the TCP connection?
2. When a chamber pressure sensor reads out of range, who decides
whether to fire `S5F1`?
3. What language does the AMHS speak to the equipment to coordinate
a FOUP handoff?
4. Is the operator at the local panel a SECS/GEM actor?
5. Is the fab planner a SECS/GEM actor?
Answers, in order:
1. The MES. The equipment is passive; it binds and waits. TCP
connect is the MES's first move.
2. The EAP (the vendor's application code on top of this library).
The SECS/GEM library doesn't know what's a "normal" pressure;
the EAP does. Once the EAP decides "this is alarm-worthy," it
calls into the alarm store and the library emits `S5F1`.
3. E84 8-line parallel I/O — physical wires, not SECS. After the
handoff, the *bookkeeping* SECS messages (`S6F11`, `S3F17`) flow
between equipment and MES.
4. Yes — through E30 §6.2 control-state transitions. Not a SECS
message *sender*, but a state-transition source the equipment
has to report on.
5. No. The planner talks to the MES; the MES talks to the EAP.
The planner is invisible from the SECS wire.
---
## What's next
You now know who's in the room and who's talking to whom. The next
chapter introduces the **vocabulary** — every SEMI acronym you'll
read in a debug log (SVID, CEID, ALID, PPID, ALCD, HCACK, T-timers,
…) — by tracing **one wafer's journey** end-to-end through a fab and
labelling every SECS message that fires along the way.
Next: [→ 03 Vocabulary + a wafer's journey](03_vocabulary_and_a_wafers_journey.md)
+592
View File
@@ -0,0 +1,592 @@
# 03 — Vocabulary + a wafer's journey
← [02 The cast of characters](02_the_cast.md) | [Back to index](00_index.md) | Next: [10 E5 — SECS-II data items](10_e5_secs_ii_data_items.md) →
The SEMI standards bury everything in acronyms. Three-letter, four-
letter, sometimes the same letter pattern (`ACKC5`, `ACKC6`, `ACKC7`,
`ACKC10`) but with completely different semantics depending on which
stream you're in. Most readers learn them by absorbing them over
years of integration work.
This chapter accelerates that. We follow **one 300 mm wafer** from
the moment it enters the fab to the moment it leaves as a finished
die, and at every step we name every acronym that fires, what it
means, and where it lives in this codebase. By the end you'll have
seen `SVID`, `CEID`, `ALID`, `PPID`, `HCACK`, `ALCD`, `RPTID`,
`OFLACK`, `MDLN`, `SOFTREV`, `CAACK`, `SMACK`, and the rest in
*context* — not as a vocabulary list.
If you're confident with the vocabulary already, skip to Part 2,
Chapter [10](10_e5_secs_ii_data_items.md) (SECS-II encoding).
---
## The setup
- **The wafer**: an unpatterned 300 mm silicon disc, 775 µm thick,
with a serial number `W-2026-06-09-A47` etched on the bevel.
- **The carrier**: a Front-Opening Universal Pod (**FOUP**) that
holds 25 wafers in vertical slots. Our wafer is in slot 14. The
FOUP's bar code reads `C-31415`.
- **The tools**:
- **PVD-1** (physical vapour deposition — deposits a metal layer)
- **LITHO-3** (photolithography — patterns the metal layer)
- **ETCH-7** (plasma etch — removes uncovered metal)
- **The host**: a fab-wide MES called `meta-fab.example`.
- **The recipe**: `RECIPE-Cu-A` for PVD-1, `RECIPE-193nm-X` for
LITHO-3, `RECIPE-CL2-B` for ETCH-7.
For brevity we'll only show the wafer's first pass through PVD-1.
The same pattern repeats for LITHO-3 and ETCH-7.
---
## Stage 1 — Establishing communications (already done)
Before any wafer arrives, **PVD-1 and meta-fab.example have already
HSMS-SELECTed each other**. That's the once-per-power-on dance:
```
host (active) equipment (passive)
───────────── ───────────────────
TCP SYN ─────────────────────────► (bind on :5000)
◄──── TCP SYN-ACK
HSMS Select.req (sessionID=0) ───►
◄──── HSMS Select.rsp (SELECT_STATUS=0=accept)
[transport state: SELECTED]
S1F13 Establish Communications ──►
◄──── S1F14 (COMMACK=0=accepted, [MDLN, SOFTREV])
[GEM communication state: COMMUNICATING]
```
This introduces three acronyms:
- **`MDLN`** — Model Name. An ASCII string up to 20 chars
identifying the equipment model. PVD-1 returns `"ACME-PVD-3000"`.
- **`SOFTREV`** — Software Revision. ASCII string up to 20 chars
identifying the firmware / EAP version. PVD-1 returns `"1.4.2"`.
- **`COMMACK`** — Communication Acknowledge. One byte; 0 = accepted,
1 = denied. Defined in E30 §6.5.
**Where:** see `equipment.yaml` device block; emission flows through
[`gem::Router`](../include/secsgem/gem/router.hpp) →
[`secsgem::secs2::Message`](../include/secsgem/secs2/message.hpp).
---
## Stage 2 — The carrier arrives at PVD-1's load port
The AMHS overhead hoist swings FOUP `C-31415` onto load port 1.
*Before* anything SECS happens, the **E84 handshake** runs on the
physical I/O lines:
```
AMHS robot load port 1
────────── ───────────
CS_0 asserted ───────────────────► (carrier select bit 0)
CS_1 asserted ───────────────────► (carrier select bit 1)
VALID asserted ──────────────────► (lines above are stable)
◄──── L_REQ asserted (LOAD allowed)
TR_REQ asserted ─────────────────► (transfer requested)
◄──── READY asserted (kinematic interlocks ok)
BUSY asserted ───────────────────► (placement in progress)
… mechanical placement happens (~5 seconds) …
BUSY de-asserted ────────────────► (placement done)
◄──── COMPT asserted (complete)
CONT asserted ───────────────────► (carrier connected to load port)
```
This introduces the E84 line-name acronyms (`VALID`, `CS_0`, `CS_1`,
`TR_REQ`, `READY`, `BUSY`, `COMPT`, `CONT`, `L_REQ`, `U_REQ`, `ES`)
and three timer names:
- **`TA1`** — armed when `VALID` asserts; the load port must respond
with `L_REQ` within `TA1`. Default ~2 seconds.
- **`TA2`** — armed when `L_REQ` asserts; `TR_REQ` must follow
within `TA2`. Default ~2 seconds.
- **`TA3`** — armed when `BUSY` asserts (transfer in progress); the
whole transfer must finish within `TA3`. Default ~60 seconds.
Any of these timing out → both sides go to `HandoffFault` and the
operator gets paged. No FOUP gets dropped because the protocol
guarantees both sides agreed on every step.
**Where:** [`include/secsgem/gem/e84.hpp`](../include/secsgem/gem/e84.hpp)
defines the FSM; [`include/secsgem/gem/e84_timers.hpp`](../include/secsgem/gem/e84_timers.hpp)
defines the timer enforcement; chapter [18](18_e84_parallel_io.md)
walks the whole handshake.
---
## Stage 3 — The carrier is on the load port; PVD-1 tells the MES
The E84 handshake gave PVD-1 a docked carrier. Now SECS messages
flow:
```
PVD-1 (equipment) meta-fab (host)
───────────────── ───────────────
S6F11 CarrierArrived ────────────►
CEID = 10001
DATAID = 1
[ {RPTID=100, V=[CarrierID="C-31415", PortID=1]} ]
◄──── S6F12 (ACKC6=0=accepted)
S3F19 Slot Map Verify ───────────►
CARRIERID = "C-31415"
[ slot_state[1..25] ]
◄──── S3F20 (SMACK=0=match)
```
New acronyms in this stage:
- **`CEID`** — Collection Event ID. An identifier (any unsigned
width; we'll use `U4`) for a noteworthy thing that happened. CEIDs
are *defined in the equipment's YAML* and the MES learns them via
`S1F23/F24`. CEID 10001 = `CarrierArrived` per E87.
- **`RPTID`** — Report ID. A bundle of variables. When CEID 10001
fires, the MES gets back the values of every variable linked to
every report linked to CEID 10001. Reports are *defined by the
host* via `S2F33` and linked to CEIDs via `S2F35`.
- **`DATAID`** — Data ID, a per-host transaction counter. Lets the
host correlate report data to a specific request.
- **`ACKC6`** — Acknowledge Code 6. S6F12 reply byte. 0 =
accepted, anything else = MES couldn't process the event.
- **`CARRIERID`** — Carrier ID, an ASCII string. Matches what the
AMHS told us via E84.
- **`SMACK`** — Slot Map Acknowledge. S3F20 reply byte. 0 =
matches what the MES expected, 1 = mismatch. Defined in E87.
- **`CAACK`** — Carrier Action Acknowledge (we'll see this one
shortly). S3F18 reply byte for carrier-action commands.
Note the pattern: every primary message ends in an odd function
(F11, F19), every reply ends in the next even function (F12, F20).
This is invariant across SECS-II. See chapter [10](10_e5_secs_ii_data_items.md)
for the encoding details.
**Where:** `gem::CarrierStore` in [`include/secsgem/gem/carrier_store.hpp`](../include/secsgem/gem/carrier_store.hpp);
the E87 wire tests in [`tests/test_e87_wire_scenarios.cpp`](../tests/test_e87_wire_scenarios.cpp).
---
## Stage 4 — The host authorises processing
```
meta-fab (host) PVD-1 (equipment)
─────────────── ─────────────────
S3F17 CarrierAction ─────────────►
CARRIERACTION = "ProceedWithCarrier"
CARRIERID = "C-31415"
◄──── S3F18 (CAACK=0=accepted)
```
- **`CARRIERACTION`** — an ASCII string from a fixed E87 set:
`ProceedWithCarrier`, `CancelCarrier`, `CarrierOut`, …
- **`CAACK`** — Carrier Action Acknowledge. S3F18 reply byte. 0 =
accepted, 1 = unknown carrier, 2 = invalid action, 3 = invalid
state, 4 = mismatch, 5 = unknown.
The host could have sent `CancelCarrier` here instead and PVD-1
would have rejected the FOUP without processing. That decision
lives entirely on the host side.
---
## Stage 5 — The host queues a process job
```
meta-fab (host) PVD-1 (equipment)
─────────────── ─────────────────
S16F11 PRJobCreate ──────────────►
PRJobID = "PJ-2026-06-09-001"
MF = "Substrate"
PRMtlOutSpec = []
PRRecipeMethod = "RecipeOnly"
RCPSpec = "RECIPE-Cu-A"
PRProcessStart = false (we'll start it explicitly later)
PRMtlnameList = ["W-2026-06-09-A47"]
◄──── S16F12 (PRJobAck=0)
S14F9 CreateControlJob ─────────►
CJobID = "CJ-2026-06-09-001"
PRJobIDList = ["PJ-2026-06-09-001"]
◄──── S14F10 (OBJACK=0)
```
New acronyms:
- **`PRJobID`** — Process Job ID, an ASCII string the host invents
for tracking. Sometimes called `PJID`.
- **`MF`** — Material Format. ASCII; `Substrate`, `Carrier`,
`SubstrateLocation`. Tells the equipment what scale the job is
about.
- **`RCPSpec`** — Recipe specification. References a recipe by ID
(`PPID`, below).
- **`PPID`** — Process Program ID. The recipe's identifier. In our
case `"RECIPE-Cu-A"`.
- **`PRJobAck`** — S16F12 reply byte. 0 = accepted, non-zero
values for each failure mode.
- **`CJobID`** — Control Job ID. A control job wraps one or more
process jobs and adds scheduling semantics (start order, abort
policy, dependency on other CJs).
- **`OBJACK`** — Object Acknowledge. S14F10 reply byte. Generic
E39 object-services ack: 0 = accepted, 1 = error.
E40 governs process jobs; E94 governs control jobs above them.
**Where:** [`include/secsgem/gem/process_jobs.hpp`](../include/secsgem/gem/process_jobs.hpp),
[`include/secsgem/gem/control_jobs.hpp`](../include/secsgem/gem/control_jobs.hpp).
The state machines are loaded from
[`data/process_job_state.yaml`](../data/process_job_state.yaml) and
[`data/control_job_state.yaml`](../data/control_job_state.yaml).
See chapter [14](14_e40_e94_jobs.md) for the lifecycle in full.
---
## Stage 6 — The host configures event reports
Before processing starts, the MES wants to subscribe to specific
events. This is a three-message dance:
```
meta-fab (host) PVD-1 (equipment)
─────────────── ─────────────────
S2F33 DefineReport ──────────────►
DATAID = 2
[ { RPTID=200, VID=[1, 2, 5] } ] (link RPTID 200 to SVIDs 1,2,5)
◄──── S2F34 (DRACK=0=accepted)
S2F35 LinkEvent ─────────────────►
DATAID = 3
[ { CEID=300, RPTID=[200] } ] (when CEID 300 fires, send RPTID 200)
◄──── S2F36 (LRACK=0=accepted)
S2F37 EnableEvent ───────────────►
CEED = true
CEID = [300] (enable CEID 300)
◄──── S2F38 (ERACK=0=accepted)
```
New acronyms:
- **`SVID`** — Status Variable ID. An identifier (`U4` typical) for
a *long-lived* value the host can read at any time — current
control state, chamber pressure, wafer counter, recipe in progress,
clock. Roughly: instance variables that survive across events.
- **`DVID`** — Data Variable ID. Same shape, but only meaningful
*at the moment an event fires*. E.g. the temperature at the time
the `ProcessStarted` event was emitted. Not readable independently
via `S1F3`; only delivered as part of a report.
- **`ECID`** — Equipment Constant ID. Same shape, but the host can
*set* it (within declared `min`/`max` bounds) via `S2F15`.
Settings that survive power-cycle: nominal chamber pressure, T3
timeout, T7 timeout, etc.
- **`VID`** — Variable ID. A generic SVID-or-DVID, used in report
definitions.
- **`CEED`** — Collection Event Enable Disable. Boolean. `true` =
enable the listed CEIDs, `false` = disable them.
- **`DRACK`** — Define Report Acknowledge. S2F34 reply.
- **`LRACK`** — Link Report Acknowledge. S2F36 reply.
- **`ERACK`** — Enable Report Acknowledge. S2F38 reply.
Acknowledge bytes are *distinct enums per stream/function*. `DRACK
= 0` = accepted; `LRACK = 0` = accepted; `ERACK = 0` = accepted —
same value, but each one has its own enumeration of failure codes
(`3 = at least one CEID does not exist`, etc.). Don't reuse one
stream's enum for another's.
**Where:** [`include/secsgem/gem/report_store.hpp`](../include/secsgem/gem/report_store.hpp),
[`include/secsgem/gem/event_store.hpp`](../include/secsgem/gem/event_store.hpp).
The configuration flow is the heart of E30 §6.6 Dynamic Event Report
Configuration; see chapter [13](13_e30_gem.md).
---
## Stage 7 — Processing begins
```
meta-fab (host) PVD-1 (equipment)
─────────────── ─────────────────
S2F41 RemoteCommand ─────────────►
RCMD = "START"
CPNAME[] / CPVAL[] = []
◄──── S2F42 (HCACK=0=accepted)
S6F11 ProcessStarted ──────────►
CEID = 300
DATAID = 4
[ {RPTID=200,
V=[ControlState="OnlineRemote",
Clock="20260609173000",
WaferCount=147]} ]
◄──── S6F12 (ACKC6=0)
```
New acronyms:
- **`RCMD`** — Remote Command name. ASCII. `"START"`, `"STOP"`,
`"PAUSE"`, `"ABORT"`, `"VENT"`, etc. Equipment-vendor-defined.
- **`CPNAME` / `CPVAL`** — Command Parameter name / value pairs.
Empty list here; some commands take parameters (e.g.
`RCMD="CHANGE-RECIPE", CPNAME="PPID", CPVAL="RECIPE-Cu-B"`).
- **`HCACK`** — Host Command Acknowledge. S2F42 reply. 0 =
accepted, 1 = invalid command, 2 = cannot perform now, 3 = at
least one parameter is invalid, 4 = accepted-and-will-finish-later,
5 = rejected, 6 = invalid object.
The `S6F11(CEID=300)` that fires next is the event report
**defined three messages earlier**. The MES correlated this by:
1. Earlier sent `S2F33` → equipment now knows that "RPTID 200 =
[SVID 1, SVID 2, SVID 5]."
2. Earlier sent `S2F35` → equipment now knows that "when CEID 300
fires, the report payload should include RPTID 200."
3. Earlier sent `S2F37` → CEID 300 is enabled, so when the
processing logic fires it, an `S6F11` actually leaves the wire.
(If CEID 300 had been left *disabled*, the processing logic
would still fire it but the wire would stay quiet.)
**Where:** [`include/secsgem/gem/host_command_registry.hpp`](../include/secsgem/gem/host_command_registry.hpp)
maps `RCMD` strings to handlers; the report-emission machinery lives
in `EquipmentDataModel` ([`include/secsgem/gem/data_model.hpp`](../include/secsgem/gem/data_model.hpp))
via `compose_reports_for(ceid)`. Wire-level tests:
[`tests/test_wire_ceid_emission.cpp`](../tests/test_wire_ceid_emission.cpp).
---
## Stage 8 — An alarm fires
Mid-processing, the chamber pressure sensor reads above its
configured `ECID="ChamberPressureMax"` threshold. The EAP's alarm
monitor decides this is alarm-worthy and calls
`alarms.set(ALID=42)`:
```
PVD-1 (equipment) meta-fab (host)
───────────────── ───────────────
S5F1 AlarmReport ────────────────►
ALCD = 0x84 (bit 7 set + category 4 = process)
ALID = 42
ALTX = "Chamber pressure above max threshold"
◄──── S5F2 (ACKC5=0)
```
- **`ALID`** — Alarm ID. An identifier (`U4` typical) for one
named alarm in the equipment's alarm directory.
- **`ALCD`** — Alarm Code. One byte. Bit 7 = "set" (1) or "clear"
(0). Lower 7 bits = category (1 = personal safety, 2 = equipment
safety, 3 = parameter control warning, 4 = parameter control
error, 5 = irrecoverable error, 6 = equipment status warning, 7 =
attention flag, 8 = data integrity, others reserved). E5 §13.
- **`ALTX`** — Alarm Text. ASCII description, up to 120 chars per
E5 §13.
- **`ACKC5`** — Acknowledge Code 5. S5F2 reply. 0 = accepted.
If the host had previously *disabled* ALID 42 (via `S5F3
ALED=0x00`), this `S5F1` wouldn't have left the wire — the equipment
would still note the alarm internally (so `S5F5` would list it), but
the host wouldn't get pinged.
When the pressure returns to range, a second `S5F1` fires with
`ALCD=0x04` (bit 7 cleared) and the same ALID, signalling "alarm
cleared."
**Where:** [`include/secsgem/gem/alarm_store.hpp`](../include/secsgem/gem/alarm_store.hpp);
the dispatcher gates emission on the enable list in
[`include/secsgem/gem/alarm_dispatcher.hpp`](../include/secsgem/gem/alarm_dispatcher.hpp).
---
## Stage 9 — Processing completes
```
PVD-1 (equipment) meta-fab (host)
───────────────── ───────────────
S6F11 ProcessCompleted ──────────►
CEID = 301
DATAID = 5
[ {RPTID=200, V=[…]} ]
◄──── S6F12 (ACKC6=0)
```
Same pattern as `ProcessStarted` — just a different CEID for a
different lifecycle moment.
The MES sees `CEID=301` and updates its tracking: process job
`PJ-2026-06-09-001` is now `ProcessComplete` per E40. It clears its
"in progress" counter and updates wafer `W-2026-06-09-A47`'s
location in E90 substrate tracking.
---
## Stage 10 — Carrier transfers out
```
meta-fab (host) PVD-1 (equipment)
─────────────── ─────────────────
S3F25 CarrierTransfer ───────────►
CARRIERID = "C-31415"
PortID = 2 (transfer from LP1 to LP2 = outbound)
◄──── S3F26 (CAACK=0)
PVD-1 (equipment) meta-fab (host)
───────────────── ───────────────
S6F11 CarrierTransfered ─────────►
CEID = 10002
[ … ]
◄──── S6F12 (ACKC6=0)
```
Then E84 runs in reverse: the AMHS robot couples to the load port,
the parallel I/O lines hand control back, the OHT hoist lifts the
FOUP, and `C-31415` heads to LITHO-3 for the next process step.
---
## Wait, what other acronyms exist?
The journey above covered the most common acronyms but skipped a
handful that show up in other contexts. A reference list for the
rest:
### Acknowledge codes you haven't met yet
| Code | Stream | Where | 0 = accepted, then… |
|----------|--------|--------------------------------|----------------------------------------------|
| `OFLACK` | 1 | S1F16 reply to "Request Offline" | 0=accept, 1=already offline |
| `ONLACK` | 1 | S1F18 reply to "Request Online" | 0=accept, 1=not allowed, 2=already online |
| `ACKC7` | 7 | S7F4 / S7F18 (recipe send/delete) | 0=accept, 1=permission denied, 2=length err,3=matrix err,4=PPID not found,5=mode unsupported,6=other |
| `ACKC10` | 10 | S10F2 / F4 / F6 (terminal services)| 0=accept, 1=not displayed, 2=no terminal |
| `CMDA` | 2 | S2F22 reply to legacy `S2F21` | 0=ok, 1=invalid command, 2=cannot do now,3=invalid arg |
| `TIACK` | 2 | S2F32 reply to "Set Clock" | 0=accept, 1=err not done |
| `EAC` | 2 | S2F16 reply to "Set EC values" | 0=accept, 1=≥1 constant out of range, 2=busy, 3=≥1 constant unknown |
| `RSPACK` | 2 | S2F44 reply to "Set Spool Streams" | 0=accept, 1=spool not supported, 2=≥1 stream unknown |
| `RSDA` | 6 | S6F24 reply to "Spool Data Send" | 0=ok, 1=denied |
| `PPGNT` | 7 | S7F2 reply to "PP Load Inquire"| 0=permit, 1=already have, 2=no room, 3=invalid PPID, 4=mode unsupported, 5=PP non-existent, 6=other |
### Control codes you haven't met
| Code | Stream | Where | What it means |
|----------|--------|-----------------------------|----------------------------------------------------------------|
| `RSDC` | 6 | S6F23 host command to spool | 0=transmit spooled, 1=purge spool |
| `ALED` | 5 | S5F3 host enable/disable alarm | bit 7 set = enable, bit 7 cleared = disable |
| `TID` | 10 | S10F1/F3/F5 terminal display | Which terminal screen to address (0 = main) |
| `TEXT` | 10 | S10F1/F3/F5 terminal display | The ASCII text payload |
### Object-services codes (E39)
| Code | Stream | Where | What it means |
|-----------|--------|-----------------------------|--------------------------------------------------------------|
| `OBJSPEC` | various | S2F49, S14F1 | An "object specifier" — a typed path identifying a target object |
| `OBJACK` | 14 | S14F2 / F10 / F12 reply | 0=ok, 1=command failed |
| `CPACK` | 2 | S2F42 reply (modern) | Per-parameter ack for each `CPNAME/CPVAL` in the command |
| `CEPACK` | 2 | S2F50 reply (enhanced) | Per-parameter ack for enhanced remote commands |
---
## All the T-timers in one place
You'll meet timer acronyms in three different contexts. They use
the same letters with different meanings — pin them down:
### HSMS T-timers (E37 §10)
These bound the *network* behaviour. Only fire when something is
slow or stuck.
| Name | Default | What it bounds |
|------|-------------|------------------------------------------------------|
| `T3` | 45 s | Reply timeout for a W=1 primary message |
| `T5` | 10 s | How long active side waits between connect attempts |
| `T6` | 5 s | Control-transaction (Select / Linktest) reply timeout |
| `T7` | 10 s | Passive side: max time without Select.req after TCP |
| `T8` | 5 s | Max time between bytes of a single frame |
Chapter [11](11_e37_hsms.md) covers each one in detail.
### SECS-I T-timers (E4 §10)
These bound the *serial-block* behaviour. Distinct from HSMS
T-timers despite the name overlap.
| Name | Default | What it bounds |
|------|-------------|------------------------------------------------------|
| `T1` | 500 ms | Inter-character timeout within one block |
| `T2` | 10 s | Protocol timer (handshake state) |
| `T3` | 45 s | Reply timeout for a W=1 primary message |
| `T4` | 45 s | Inter-block timeout in a multi-block message |
`T3` exists in both HSMS and SECS-I with the same semantics — it's
load-bearing in both transports.
### E84 timers (E84 §6)
These bound the *physical handoff* timing. Distinct again.
| Name | Default | What it bounds |
|-------|---------|-------------------------------------------------|
| `TA1` | ~2 s | `VALID``L_REQ` |
| `TA2` | ~2 s | `L_REQ``TR_REQ` |
| `TA3` | ~60 s | `BUSY` → transfer complete |
### E30 communication-state timers (E30 §6.5)
These bound the *application-level* establish-communications loop:
| Name | Default | What it bounds |
|-----------|---------|-----------------------------------------------------------------|
| `T_CRA` | 45 s | Wait for `S1F14` (Comm Request Acknowledge) reply after `S1F13` |
| `T_DELAY` | 10 s | Retry interval after a failed `S1F13` round-trip |
Defined in [`include/secsgem/gem/communication_state.hpp`](../include/secsgem/gem/communication_state.hpp);
tested in [`tests/test_communication_state.cpp`](../tests/test_communication_state.cpp).
---
## Stream-by-stream summary
The streams you'll meet most often, with one sentence each:
| Stream | What it's for | Most-used messages |
|--------|----------------------------------------------------------|---------------------------------------------------|
| S1 | Identification, status, control | `S1F1/F2`, `S1F3/F4`, `S1F11/F12`, `S1F13/F14`, `S1F15-F18`, `S1F19/F20`, `S1F21-F24` |
| S2 | Equipment constants, clock, events, commands | `S2F13-F18`, `S2F29-F38`, `S2F41/F42`, `S2F43-F50` |
| S3 | Carrier management (E87) | `S3F17/F18`, `S3F19/F20`, `S3F25-F28` |
| S5 | Alarms, exception recovery | `S5F1-F8`, `S5F9-F18` |
| S6 | Data collection, event reports, spool | `S6F11/F12`, `S6F15/F16`, `S6F19-F22`, `S6F23-F26` |
| S7 | Recipe / process program management | `S7F1-F6`, `S7F17-F20`, `S7F23-F26` |
| S9 | Protocol-error reports (auto-emitted by equipment) | `S9F1`, `S9F3`, `S9F5`, `S9F7`, `S9F9`, `S9F11`, `S9F13` |
| S10 | Terminal services | `S10F1-F6` |
| S12 | Wafer maps | (per-stream — chapter [10](10_e5_secs_ii_data_items.md) §6) |
| S14 | Generic object services (E39), control jobs (E94) | `S14F1/F2`, `S14F9-F12` |
| S16 | Process jobs (E40) | `S16F5-F8`, `S16F9`, `S16F11-F14`, `S16F27/F28` |
Where every named message lives in code: [`build/generated/secsgem/gem/messages.hpp`](../build/generated/secsgem/gem/messages.hpp)
(after a build) — generated from
[`data/messages.yaml`](../data/messages.yaml) by
[`tools/generate_messages.py`](../tools/generate_messages.py).
Chapter [31](31_spec_as_data_and_codegen.md) walks the codegen.
---
## You've made it through Part 1
You can now:
- Explain why SECS/GEM exists, and what each of "SECS", "HSMS", and
"GEM" actually refers to.
- Name every actor in the fab automation stack and describe who
talks to whom.
- Recognise every common acronym (SVID, ECID, DVID, CEID, RPTID,
ALID, PPID, MDLN, SOFTREV, HCACK, ALCD, OFLACK, …) and the
acknowledge bytes that go with each stream.
- List the T-timers in four different contexts (HSMS, SECS-I, E84,
E30 communication state) and not confuse them.
Part 2 of this guide takes one standard at a time, from the
ground up: byte-level encoding, wire diagrams, every message, every
ack value, the FSMs, and the code that implements each. We start
with the foundation everything else stands on — **E5 SECS-II**, the
data-item encoding.
Next: [→ 10 E5 — SECS-II data items](10_e5_secs_ii_data_items.md)
View File
View File
View File
View File