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