Files
secs-gem/docs/15_e87_carriers.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

307 lines
10 KiB
Markdown

# 15 — E87: Carriers and load ports
← [14 E40 + E94 — Process and control jobs](14_e40_e94_jobs.md) | [Back to index](00_index.md) | Next: [16 E90 + E157 — Substrate and module tracking](16_e90_e157_substrates_modules.md) →
Before a wafer can be processed it has to physically arrive at the
tool, dock with the load port, expose its slot contents, get
verified, and be authorised by the host. That's six distinct
state transitions, each one tracked by SEMI **E87 — Carrier
Management** (2000).
This chapter covers:
- The carrier (FOUP) and the load port — what each *is* and what
state they each track.
- The three carrier state sub-machines: ID, Slot Map, Access.
- The three load-port state sub-machines: Transfer, Reservation,
Association.
- The E87 message catalog (S3F*).
- Where each piece is in code.
---
## Vocabulary
- **Carrier** — a physical container holding wafers. In a 300 mm
fab almost always a **FOUP** (Front-Opening Universal Pod, 25
slots). In smaller fabs sometimes a SMIF pod or a cassette.
- **Load port** — the equipment-side dock where carriers physically
attach. A typical PVD tool has 2 load ports (1 input, 1 output);
a lithography stepper might have 4.
- **Slot map** — the equipment's reading of which of the carrier's
N slots contain wafers, expressed as N bytes. Slot states are
Empty, CorrectlyOccupied, DoubleSlotted, CrossSlotted, NotRead.
- **Carrier ID** — bar-coded string on the FOUP (e.g. "C-31415").
Read by the equipment's bar-code reader on docking.
---
## The three carrier state sub-machines
E87 doesn't track "carrier state" as one variable — it tracks
three orthogonal aspects:
### 1. ID Status
How sure are we who this carrier is?
```cpp
// include/secsgem/gem/carrier_state.hpp:23
enum class CarrierIDStatus : uint8_t {
NotConfirmed = 0,
WaitingForHost = 1,
Confirmed = 2,
IDVerificationFailed = 3,
};
```
Bar-code reader fires `ID_READ_OK` → NotConfirmed → Confirmed.
If the reader fails or returns gibberish, `ID_READ_FAIL`
NotConfirmed → IDVerificationFailed. Some hosts insist on
verifying the ID themselves (look it up against their LMS); they
hold the carrier in WaitingForHost until they reply with
`HOST_ID_CONFIRMED`.
### 2. Slot Map Status
Have we read the carrier's contents?
```cpp
// include/secsgem/gem/carrier_state.hpp:45
enum class SlotMapStatus : uint8_t {
NotRead = 0,
WaitingForHost = 1,
Read = 2,
SlotMapVerificationFailed = 3,
};
```
The mapper (an optical sensor reading wafer positions) runs after
ID confirmation. Result: a 25-byte vector (one byte per slot).
Hosts can validate the map against expectation (S3F19 Slot Map
Verify); a mismatch flips to `SlotMapVerificationFailed`.
### 3. Access Status
Is the carrier authorised for processing right now?
```cpp
// include/secsgem/gem/carrier_state.hpp:64
enum class CarrierAccessStatus : uint8_t {
NotAccessed = 0,
InAccess = 1,
CarrierComplete = 2,
CarrierStopped = 3,
};
```
`InAccess` means the equipment is currently using slots from this
carrier. `CarrierComplete` means done; awaiting unloading.
All three sub-machines progress **independently**. A carrier can
be `Confirmed` (ID) + `Read` (Map) + `NotAccessed` (Access) — and
that's the typical state after docking but before the host
authorises processing.
`CarrierStateMachine` in
[`include/secsgem/gem/carrier_state.hpp`](../include/secsgem/gem/carrier_state.hpp)
composes the three; tests in
[`tests/test_carrier_state.cpp`](../tests/test_carrier_state.cpp)
(11 cases) and
[`tests/test_e87_wire_scenarios.cpp`](../tests/test_e87_wire_scenarios.cpp)
(4 wire scenarios).
---
## The three load-port state sub-machines
The load port has its own three sub-machines:
### 1. Transfer State
```cpp
// include/secsgem/gem/load_port_state.hpp:19
enum class LoadPortTransferState : uint8_t {
OutOfService = 0,
ReadyToLoad = 1,
ReadyToUnload = 2,
InService = 3,
};
```
Physical readiness — is the port mechanically ready to dock a new
FOUP, release the current one, or busy?
### 2. Reservation Status
```cpp
enum class LoadPortReservationStatus : uint8_t {
NotReserved = 0,
Reserved = 1,
};
```
Has the host pre-reserved this port for an inbound carrier?
Reservation is how the host tells the AMHS "send the carrier to
this specific port."
### 3. Association Status
```cpp
enum class LoadPortAssociationStatus : uint8_t {
NotAssociated = 0,
Associated = 1,
};
```
Is there a known Carrier object linked to this port? Becomes
`Associated` when a carrier docks and the ID is read.
`LoadPortStateMachine` in
[`include/secsgem/gem/load_port_state.hpp`](../include/secsgem/gem/load_port_state.hpp).
---
## Multi-port + multi-carrier
A real fab tool runs **multiple load ports in parallel**. A
4-port cluster tool can have:
- Port 1: carrier A in `Associated` + `InAccess` (processing)
- Port 2: carrier B in `Associated` + `CarrierComplete` (done,
awaiting unload)
- Port 3: AMHS robot docking carrier C (`InService`)
- Port 4: `OutOfService` (mechanical fault)
The codebase models this as a **per-port store**:
```cpp
// include/secsgem/gem/store/carriers.hpp
class CarrierStore {
// one CarrierStateMachine per Carrier ID
// one LoadPortStateMachine per PortID
};
```
Tests for the parallel scenario:
[`tests/test_e87_wire_scenarios.cpp`](../tests/test_e87_wire_scenarios.cpp)
(4 multi-port scenarios — independence between ports asserted).
---
## The E87 messages
| S/F | Direction | Purpose |
|-------|-----------|--------------------------------------------------|
| S3F17 | H → E | CarrierAction. Body: CARRIERACTION + CARRIERID. |
| S3F18 | E → H | CAACK reply. |
| S3F19 | H → E | Slot Map Verify. Body: CARRIERID + expected slot states. |
| S3F20 | E → H | SMACK reply. |
| S3F25 | H → E | Carrier Transfer. Move a carrier between ports. |
| S3F26 | E → H | CAACK reply. |
| S3F27 | H → E | Cancel Carrier. Pre-arrival cancellation. |
| S3F28 | E → H | CAACK reply. |
### CARRIERACTION strings (S3F17 body)
The dominant E87 messages are S3F17 carrier-action commands.
CARRIERACTION is an ASCII string from a fixed E87 set:
| String | Meaning |
|-------------------------|------------------------------------------------------|
| `ProceedWithCarrier` | Authorise processing. Triggers Access → InAccess. |
| `CancelCarrier` | Refuse the carrier; equipment doesn't process it. |
| `CancelCarrierAtPort` | Same but specifies port. |
| `BypassCarrier` | Process nothing from this carrier (audit slot map only). |
| `CarrierOut` | Mark `CarrierComplete`; AMHS will retrieve. |
| `CarrierReCID` | Re-read the carrier ID (e.g. bar code was iffy). |
CAACK reply codes (S3F18, 1 byte):
| Code | Meaning |
|------|--------------------------------------------------|
| 0 | Acknowledged. |
| 1 | Invalid command. |
| 2 | Cannot perform now. |
| 3 | Invalid carrier ID. |
| 4 | Invalid port ID. |
| 5 | Carrier ID unknown. |
---
## A typical carrier flow
```
1. AMHS docks FOUP at port 1.
2. Equipment fires CarrierArrived event (CEID per equipment.yaml)
→ S6F11 (host gets pinged).
3. Equipment reads bar code → CarrierIDStatus: NotConfirmed → Confirmed.
4. Equipment runs slot mapper → SlotMapStatus: NotRead → Read.
5. (Optional) Host sends S3F19 SlotMapVerify with expected contents
→ equipment compares → SMACK = 0 (match) or 1 (mismatch).
6. Host sends S3F17 CARRIERACTION = "ProceedWithCarrier"
→ CarrierAccessStatus: NotAccessed → InAccess.
7. Processing happens. Substrate state changes are tracked by E90
(chapter 16).
8. All substrates done. Equipment fires CarrierComplete event.
9. Host sends S3F17 CARRIERACTION = "CarrierOut"
→ CarrierAccessStatus → CarrierComplete.
10. AMHS retrieves FOUP from port 1. LoadPortAssociation goes back
to NotAssociated; carrier object can be deleted.
```
The slot-map-mismatch path in step 5 is tested by
[`tests/test_e87_wire_scenarios.cpp`](../tests/test_e87_wire_scenarios.cpp);
the happy path by
[`tests/test_carriers.cpp`](../tests/test_carriers.cpp) (6 cases).
---
## Slot maps in detail
A slot map is a **byte-vector** with one byte per carrier slot.
For a 25-slot FOUP, that's 25 bytes. Byte values:
| Value | State | Meaning |
|-------|--------------------|------------------------------------------------|
| 0 | `Empty` | No wafer detected. |
| 1 | `CorrectlyOccupied`| One wafer in the correct vertical position. |
| 2 | `DoubleSlotted` | Two wafers in one slot — physical fault. |
| 3 | `CrossSlotted` | Wafer at an angle / wrong height. |
| 4 | `NotRead` | Sensor couldn't read this slot. |
S3F19 (Slot Map Verify) carries the host's *expected* slot map.
The equipment compares against its read map; if they match,
SMACK = 0. Mismatches imply someone interfered with the carrier
(or the mapper is broken).
The map is part of the carrier store and persists across restarts.
---
## Persistence
Like all GEM-300 stores, the carrier store is **persistent**. A
file per active carrier + a file per port lets the equipment
recover its full E87 state after a restart — including in-progress
carriers stranded mid-Access by a power loss.
Tested by
[`tests/test_carrier_persistence.cpp`](../tests/test_carrier_persistence.cpp)
(6 cases — write, restart, replay, corrupted-file drop, removal).
Per-store journal pattern is the same across E40, E87, E90, E94,
E116; chapter [36](36_persistence_validation_metrics.md) walks the
mechanism.
---
## Where to go next
You now know how a carrier arrives, gets authorised, and leaves.
But every wafer *inside* the carrier needs its own tracking — and
when wafers move into a process module, their state has to follow
them. That's **E90 and E157**.
Next: [→ 16 E90 + E157 — Substrate and module tracking](16_e90_e157_substrates_modules.md)